Ember Nested Model: Find and remove child element from model - javascript

I have a nested model which is used to construct the tree view.
Here, I have two action handlers which is used to delete the folders based on the passed "id" param.
So when I pass the first level folder "id", I can remove the elements from the model using filterBy().
But when I pass the second/third level folder "id", and use filterBy to filter through the model using "id", it returns an empty value.
Here, how can I can filter through the sub level model using id?
In Template:
<script type="text/x-handlebars" data-template-name="index">
<button {{action deleteFolder_1 '1'}}>Delete Folder_1</button>
<br><br>
<button {{action deleteFolder_11 '11'}}>Delete Folder_11</button>
<ul>
{{#each model}}
{{partial "tree"}}
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" data-template-name="_tree">
<li>{{foldername}}</li>
<ul>
{{#each children}}
{{partial "tree"}}
{{/each}}
</ul>
</script>
In app.js
App.IndexRoute = Ember.Route.extend({
model: function() {
return [
{
id: '1',
foldername: 'Folder 1',
children:[{
id: '11',
foldername: 'Sub Folder 11',
children: [{
id: '111',
foldername: 'Sub Folder 111',
children: []
}]
}]
},
{
id: '2',
foldername: 'Folder 2',
children: []
},
{
id: '3',
foldername: 'Folder 3',
children:[]
}];
},
actions: {
deleteFolder_1: function (id) {
var obj = this.controller.content.filterBy('id',id);
this.controller.content.removeObjects(obj);
},
deleteFolder_11: function (id) {
var obj = this.controller.content.filterBy('id',id);
this.controller.content.removeObjects(obj);
// How can I delete the subfolder here? How can I find the sub id? Is there any way to find id recursively.
}
}
});
JSBIN DEMO: LINK

You will need to recursively traverse the tree. Here is a function that will delete the entry based on the id you pass. This function will traverse the whole tree to find a match.
deleteFolderById: function (id) {
function recursiveFilter(item, index, array) {
item = Em.Object.create(item);
if (item.id === id) {
return null;
} else {
item.set('children', item.children.map(recursiveFilter).compact());
return item;
}
}
var filtered = this.get('controller.content').map(recursiveFilter).compact();
this.set('controller.content', filtered);
}
Here is the working demo.

Related

How to update a recurring nested object property using its previous value using Javascript?

I got an array with some elements and each element has some properties like name, id and children. Each element has children property and then sub children so on and so forth. This array is already there in our application. Now I want to update all of elements and sub elements of our array with a new property called "path". The "path" will be the key and its value will be the addition of its previous object's name. For example
let dataArray = [
{
name: 'Data',
id: 12,
path: 'Data',
children: [
{
name: 'Data Center',
id: 13,
path: 'Data/Data Center',
children: [
{
name: 'Data Management',
id: 13,
path: 'Data/Data Center/Data Managemet',
},
],
},
],
},
{
name: 'Machine',
id: 12,
path: 'Machine',
children: [
{
name: 'Machine Center',
id: 13,
path: 'Machine/Machine Center',
children: [
{
name: 'Machine Management',
id: 13,
path: 'Machine/Machine Center/Machine Managemet',
},
],
},
],
}
];
As you can see path property to all the elements. I want to make this happen programatically. For that, here is the piece of code I have written but I am not able to understand what need to need to be done to achieve. This piece of code adds path property to all the elements and sub elements but with name as path like above example. I want the addition of previous names which have been traversed.
addPathProperty(){
dataArray.forEach((element) =>{
element.path = element.name;
if(element.children.length > 0){
this.addPathProperty(element.children)
}
});
}
Here is the link Stackblitz. Any help will be appreciated. Thank you
I think this code should work:
const appendPath = (root = '') => (obj) => {
obj.path = root ? `${root}/${obj.name}` : obj.name
if (obj.children) {
obj.children.forEach(appendPath(obj.path))
}
}
dataArray.forEach(appendPath())
appendPath is a function that accepts a root, when this function is invoked, it will combine root and name and set this to object.path; After that, it will do the same for the object.children if they exists

Deleting a child from a nested object structure in JavaScript [duplicate]

In my application I create a JavaScript object based on a JSON similar to this:
{
name: "root",
id: 112,
children: [
{
name: "child one",
id: 231,
children: [
{name: "grand child 1", id: 334, children: []},
{name: "grand child 2", id: 784, children: []}
]
},
{
name: "child two",
id: 343,
children: []
}
]
}
How can I remove any Child by his id? Please note that I don’t know the static path to the node e.g id == 334 so I am wondering how I could remove that node with just knowing it's id.
function del(obj,id){
obj.children = obj.children.filter(el => {
if(el.children) del(el,id);//delete subnodes
return el.id !== id; //delete this
});
}
A recursive approach to traverse the objects, usable as:
del(
{ children:[ { id:1 }, { id:2, children:[{id:1}] }] },
1
);
check this :
var abc = your json object;
//now filter the item based on id
abc = jQuery.grep(
abc.children,
function (item,index) {
return item.id != "343";
});
hope it helps.
var delIt = function (obj,id) {
return obj.children = obj.children.filter(function (child) {
if(child.children) delIt(child,id);
return child.id!=id;
});
}
var x= delIt(Tobj,335);
You can use filter function to collect items which are not equals to given id

How to access to item from HTML Node [Vue.js]

I have:
Vue app data:
data: function() {
return {
items: [
{id: 1, text: 'one', other_data: {}},
{id: 2, text: 'two', other_data: {}},
{id: 3, text: 'three', other_data: {}}
]
}
}
Template:
<div v-for="item in items" id="my_items">
<span>{{ item.text }}</span>
</div>
And i need to access from one item by external JS code like next:
let item_node = document.getElementById('my_items').children[1]; // get 2nd child node of #my_items
item_node.__vuedata__ // must be a 2nd item from items in Vue data. {id: 2, text: 'two'...
How to do like this?
Vue recommends to use ref over ID and classes for DOM references. We use ref as a replacement for id. Since it is used on top of the v-for directive, all child elements are now referenced as an array of the ref. So every span will now be hello[0] - hello[n] The mounted code outputs the item.text for first child as it uses 0.
I added a click listener, every time you click the element, the value of item is passed to the method. Here you can extract all values and do whatever manipulation you require. Hope this is what you are looking for.
<template>
<div id="app">
<div v-for="item in items" :key="item.id" ref='hello' #click="logItem(item)">
<span>{{ item.text }}</span>
</div>
</div>
</template>
<script>
export default {
name: "App",
data: function() {
return {
items: [
{id: 1, text: 'one', other_data: {}},
{id: 2, text: 'two', other_data: {}},
{id: 3, text: 'three', other_data: {}}
]
}
},
methods: {
logItem: (item) => console.log(item)
},
mounted: function() {
console.log(this.$refs.hello[0].innerText)
}
};
</script>

Implementing an accordion in AngularJS and better understanding $scope

I am editing an already existing Angular webpage. In the app.js file, there are multiple controllers for each page. Here's their template:
JS:
app.controller("myCtrl", function($scope) {
$scope.useSettings({
Header: "header",
Title: "title",
mainImg: "main.png",
fields: [{
fieldText: "text",
},{
fieldImg: "pic.jpg",
},{
fieldAccordion:
//
},{
]
});
});
My goal is to have fieldAccordion work as an accordion for the data that I input there, just how all my fields work.
This is my Unordered List data type in the html file:
<div class="basic-page__field__undordered" ng-
if="field.fieldUnorderedList">
<ul class="field-list field-list--unordered">
<li ng-repeat="listItem in field.fieldUnorderedList">{{ listItem }}
</li>
</ul>
</div>
I can simply use it in the .JS file like this:
fieldUnorderedList: [
"item1",
"item2",
]
So I would like to find a similar way to use data for an accordion type.
Here is how I wrote it in the .html file:
<div class="basic-page__field__accordion" ng-if="field.fieldAccordion">
<ul class="field-list field-list--accordion">
<li ng-repeat="item in items" ng-click="accordion.current = item.name">
{{item.name}}
<ul ng-show="accordion.current == item.name">
<li ng-repeat="sub in item.sub">{{sub.name}}</li>
</ul>
</li>
</ul>
</div>
Now the issue that I'm having is getting it to work as simple as using
"fieldAccordion:" in my controller.
I have done some research and what I don't understand is this:
All my controllers have the same template with $scope.useSettings, followed by all the data such as fieldText, fieldImg, etc.
How would I add another $scope that deals with the accordion data type which I can use in the exact place that I want it to be?
Something like this:
$scope.accordion = {
current: null
};
$scope.items = [{
name: 'List 1',
sub: [{
name: 'Sub 1.1'
},{
name: 'Sub 1.2'
}]
},{
name: 'List 2',
sub: [{
name: 'Sub 2.1'
}]
},{
name: 'List 3',
sub: [{
name: 'Sub 3.1'
},{
name: 'Sub 3.2'
},{
name: 'Sub 3.3'
}]
}];
}]);
This is one of my biggest obstacles in getting to understand AngularJS. Since I am working on an already existing webpage, I don't know how to add multiple $scopes for one controllers and where to use them. Any help is appreciated.

List JS object array

I have a an array in js it's something like this
menu = [
{
name: 'Item1',
//submenuName: 'submenu-1',
},
{
name: 'Item2',
submenuName: 'submenu-2',
sub: [
{
name: 'Item2_1',
//submenuName: '',
},
{
name: 'Item2_2',
//submenuName: '',
},
{
name: 'Item2_3',
//submenuName: '',
}
]
},
{
name: 'Item3',
//submenuName: 'submenu-3',
}
]
And i need to list them in ul tag, but every level has to be closed before the other.
<ul data-menu="main">
<li data-submenu>Item1</li>
<li data-submenu='submenu-2'>Item2</li>
<li data-submenu>Item3</li>
</ul>
<ul data-menu="submenu-2">
<li data-submenu>Item2_1</li>
<li data-submenu>Item2_2</li>
<li data-submenu>Item2_3</li>
</ul>
and so on. I've mange to print them but only the first level. Cannot print the sub level.
If the menus need to be listed one after another and not nested, then maintain an array of menus to print and fill that array with submenus while printing parent menus.
Since you mentioned, that you're using jQuery, here is an example using this library.
function generateMenu (menu, container) {
var menus = [{name: 'main', entries: menu}];
while (menus.length) {
var current = menus.shift();
var ul = $("<ul />").attr('data-menu', current.name);
$.each(current.entries, function (index, menuItem) {
var li = $('<li />')
.attr('data-submenu', menuItem.submenuName || '')
.text(menuItem.name);
if ($.isArray(menuItem.sub)) {
menus.push({name: menuItem.submenuName, entries: menuItem.sub});
}
li.appendTo(ul);
});
ul.appendTo(container);
}
}
generateMenu(menu, $('body'));
JSFiddle example

Categories

Resources