Angularjs delete JSon object - javascript

So I have little dilemma here. I have a nested json object that is inside ng-repeat and is sortable using AngularJS UI Sortable (based on JQuery UI Sortable):
$scope.rootItem = {
id: '1',
type: 'course',
title: 'Adobe Photoshop CC for beginners',
items: [{
id: '2',
type: 'label',
title:'label',
items:[{
id: '3',
type: 'module',
title:'Module title',
items: [{
id: '4',
type: 'topic',
title:'Topic title',
items: [{
id: '5',
type: 'content',
title:'Content title'
}, {
id: '6',
type: 'content',
title:'Content title'
}]
}]
},{
id: '7',
type: 'resources',
title:'Resources'
},{
id: '8',
type: 'module',
title:'Module title',
items: [{
id: '9',
type: 'topic',
title:'Topic',
items: [{
id: '10',
type: 'question',
title:'Question title'
}]
}, {
id: '11',
type: 'topic',
title:'Topic title',
items: [{
id: '12',
type: 'content',
title:'Content title'
}]
}]
}]
},{
id: '14',
type: 'assessmentLabel',
title: 'Assessment Label',
items: [{
id: '15',
type: 'assessment',
title: 'Assessment Title',
items: [{
id: '16',
type: 'courseAssessment',
title: 'Course Question Group',
items: []
}]
}]
}]
};
What I should be able to do is remove any of the items within this object, and if it has any children they need to be remove too.
So what I would generally think is passing either $index and use splice to remove it (if it was an array).
But for objects doesnt seem to work this way, I read online that delete should be used instead...
On my button I try to pass the object itself as in:
data-ng-click="removeItem(ngModelItem)"
and in my controller do something like this:
// Remove Item from the list
$scope.removeItem = function(item) {
};
Any suggestions?

Use ngModelItem
<li ng-repeat="innerItem in ngModelItem.items">
Delete me
in your controller,
$scope.deleteItem = function(collection, index){
collection.splice(index,1);
};
Demo

For removing json elements from a json object you use the delete operator. But in your case, I assume you want to remove a json object from a json array, so you should really use splice() instead.
You should receive both the list and the index in your removeItem() function so you can remove the indexed element and angularjs will update your view.

Related

Changing nested object's structure

I want to convert an object from one format to another. So far my attempts at doing this recursively failed; either I'm getting a maximum stack exception or I'm unable to iterate over all paths.
Let's assume we have an object that lists questions and their answers. There may be N questions and M answers.
Object at start:
var before = {
item: 'Question 1',
id: '1',
type: 'Question',
items: [
{
item: 'Answer 1',
id: '1.1',
type: 'Answer',
items:
[
{
item: 'Question 2',
id: '1.1.1',
type: 'Question',
items: [
{
item: 'Answer 2.1',
id: '1.1.1.1',
type: 'Answer'
},
{
item: 'Answer 2.2',
id: '1.1.1.2',
type: 'Answer'
}
]
}
// ...
]
}, {
item: 'Answer 1',
id: '1.2',
type: 'Answer',
items:
[
{
item: 'Question 3',
id: '1.2.1',
type: 'Question',
items: [
{
item: 'Answer 3.1',
id: '1.2.1.1',
type: 'Answer'
},
{
item: 'Answer 3.2',
id: '1.2.1.2',
type: 'Answer'
}
]
}
// ...
]
}
// ...
]
}
Object how it should look like (wrap all in 'items' array; change key names 'item' to 'title', 'id' to 'key', remove 'type', add 'color' depending on 'type'):
var after = {
items: [
{
title: 'Question 1',
key: '1',
color: 'Red',
items: [
{
title: 'Answer 1',
key: '1.1',
color: 'Blue',
items:
[
{
title: 'Question 2',
key: '1.1.1',
color: 'Red',
items: [
{
title: 'Answer 2.1',
key: '1.1.1.1',
color: 'Blue'
},
{
title: 'Answer 2.2',
key: '1.1.1.2',
color: 'Blue'
}
]
}
// ...
]
}, {
title: 'Answer 1',
key: '1.2',
color: 'Blue',
items:
[
{
title: 'Question 3',
key: '1.2.1',
color: 'Red',
items: [
{
title: 'Answer 3.1',
key: '1.2.1.1',
color: 'Blue'
},
{
title: 'Answer 3.2',
key: '1.2.1.2',
color: 'Blue'
}
]
}
// ...
]
}
// ...
]
}
]
}
It seems easy enough, but I can't get it to work. This is how I tried to iterate:
function iter(o) {
for(let k in o) {
if (!(['item', 'items', 'id'].includes(k))) // My object contains a few more keys I don't want to go down further into
continue
if (o[k] !== null && typeof o[k] === 'object') {
iter(o[k]); // Max stack exception
return;
}
}
};
Thank you very much!
You could map the objects and rename the keys and map nested items.
const
iter = ({ item: title, id: key, type, items, ...o }) => ({
title,
key,
color: 'color' + key,
...o,
...(items && { items: items.map(iter) })
}),
before = { item: 'Question 1', id: '1', type: 'Question', items: [{ item: 'Answer 1', id: '1.1', type: 'Answer', items: [{ item: 'Question 2', id: '1.1.1', type: 'Question', items: [{ item: 'Answer 2.1', id: '1.1.1.1', type: 'Answer' }, { item: 'Answer 2.2', id: '1.1.1.2', type: 'Answer' }] }] }, { item: 'Answer 1', id: '1.2', type: 'Answer', items: [{ item: 'Question 3', id: '1.2.1', type: 'Question', items: [{ item: 'Answer 3.1', id: '1.2.1.1', type: 'Answer' }, { item: 'Answer 3.2', id: '1.2.1.2', type: 'Answer' }] }] }] },
result = [before].map(iter);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can achieve this using map, I wrote an simple test to show my point here
this is the important part of the code
function rename(item: any) {
return {
title: item.item,
key: item.id,
color: item.type === 'Question' ? 'red' : 'blue',
items: item.items?.map(rename)
}
}
console.log(items.map(rename))
Of course if you're using typescript, change any to the appropriate type and pay attention that I'm using ? operator which will not work with javascript, so you could do something like
...
items: item.items ? item.items.map(rename) : undefined
...

Javascript - Assign object item with values from another object

I have an object with few items and I want to update the values of one property options from another object.
Object 1 :
structure = [
{
id: 'name',
label: 'Name',
filterType: 'text',
filterOn: 'contains'
},
{
id: 'address',
label: 'Address',
filterType: 'text',
filterOn: 'contains'
},
{
id: 'phone',
label: 'Phone',
filterType: 'select',
filterOn: 'contains',
options: [{ label: 'abc', value: 'abc' },
{ label: 'xyz', value: 'xyz' },
{ label: 'mno', value: 'mno' }]
}
];
if the id is phone then I want to get the values from the object 2 phoneList and assign it to the options instead of hard coding it.
options: [{ label: 'abc', value: 'abc' },
{ label: 'xyz', value: 'xyz' },
{ label: 'mno', value: 'mno' }]
}
];
object 2 is
this.props.phoneList = [{name: 'aaa', age: 11},{name : 'bbb' , age : 12}, and so on
]
label and values will be this.props.phoneList[i].name
how to loop over this and get the latest values from the other object
First use filter to identify the object with id phone. Then use map to transform this.probs.phoneList in the desired format and assign to options.
structure.filter (x => x.id == 'phone')[0].options = this.probs.phoneList.map (x => ({label: x.name, value: x.name}));

CKEditor - getting the default toolbar array BACK prior to instantiation

I have not found this on the CKEditor documentation and am reasonably sure it can be accomplished. I know that if you explicitly pass toolbar: to CKEditor when instantiating an editor, you can control the toolbar config.
What I would like to do is first GET the default toolbar and remove buttons based on some user requirements. How would I fetch the CKEDITOR toolbar array first? Thanks!
(NOTE: I thought it might be found in CKEDITOR.config.toolbar_Full like in this link here but that shows as undefined; I'm using CKE 4.7.1.)
(NOTE 2: Turns out you can construct a menu negatively using the removeButtons attribute, but I'll leave the question up as getting the entire menu as an array might still be useful. Example:
CKEDITOR.replace('my-region', { removeButtons: 'Save,Copy,About,Source' });
)
I am also using version 4.7.1. I also have customized toolbars related to user requirements. Here is the snippet
In the config definition:
initToolbarSets(config);
config.toolbar = 'Default';
The function for building the different toolbar sets(including custom plugins):
function initToolbarSets(config) {
config.toolbar_Empty = [];
config.toolbar_Default = [
{ name: '1', items: ['Cut','Copy','Paste','PasteText','PasteFromWord'] },
{ name: '2', items: ['Undo','Redo','-','Find','Replace'] },
{ name: '3', items: ['DataTF', 'DataCB', 'Select', 'Table', 'Image','HorizontalRule'] },
'/',
{ name: '4', items: ['Bold','Italic','Underline','Strike'] },
{ name: '5', items: ['NumberedList','BulletedList','-','Outdent','Indent'] },
{ name: '6', items: ['JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock'] },
{ name: '7', items: ['SpecialChar','PageBreak'] },
{ name: '8', items: ['Font','FontSize'] },
{ name: '9', items: ['TextColor','BGColor'] },
{ name: '10', items: ['Maximize'] }
];
config.toolbar_Mail= [
{ name: '1', items: ['Cut','Copy','Paste','PasteText','Preview'] },
{ name: '2', items: ['Undo','Redo','-','Find','Replace'] },
{ name: '3', items: ['TextField', 'Checkbox', 'Select', 'Table','Image', 'HorizontalRule', 'Link'] },
'/',
{ name: '4', items: ['Bold', 'Italic', 'MyPlugin', 'Strike', 'Subscript', 'Superscript', 'RemoveFormat'] },
{ name: '5', items: ['NumberedList','BulletedList','-','Outdent','Indent'] },
{ name: '6', items: ['JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock'] },
{ name: '7', items: ['SpecialChar', 'PageBreak'] },
{ name: '8', items: ['Font', 'FontSize', 'Styles'] },
{ name: '9', items: ['TextColor','BGColor'] },
{ name: '10', items: ['Maximize'] }
];
config.toolbar_Helpdoc= [
{ name: '1', items: ['Cut', 'Copy', 'Paste', 'PasteText', 'Preview'] },
{ name: '2', items: ['Undo', 'Redo', '-', 'Find', 'Replace'] },
{ name: '3', items: ['Table', 'HorizontalRule', 'Link'] },
'/',
{ name: '4', items: ['Bold', 'Underline', 'Strike', 'MyPlugin', 'Superscript', 'RemoveFormat'] },
{ name: '5', items: ['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent'] },
{ name: '6', items: ['JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock'] },
{ name: '7', items: ['SpecialChar', 'PageBreak'] },
{ name: '8', items: ['Styles']},
{ name: '9', items: ['Maximize'] }
];
//...some other toolbar profiles
};
Now it depends on you how to continue. Either you develope a logic (probably within the config) to decide which toolbar profile has to be shwon to the current user. Another approach could be a control that encapsulates the whole CKEditor. In my case (ASP.NET) the editor is embedded like this:
<mynamespace:MyHTMLTextEditor ID="ckE" runat="server" Toolbar="Helpdoc" StylesSet="helpdoc" ForcePasteAsPlainText="true"/>

How to change single-dimension json array or object into multi-dimension

Using Javascript or jQuery how can I change single-dimension array into multi-dimension array or nested array. Please assume that the nesting can go infinite level deeper.
This is how I get the data:
var beforeModifiedObj = [
{
id: '1',
title: 'Awesome Group',
type: '1',
parentID: '0',
},
{
id: '2',
title: 'Rockers',
type: '2',
parentID: '0'
},
{
id: '3',
title: 'Dazzlers',
type: '3',
parentID: '0'
},
{
id: '4',
title: 'Rock-n-Rolla',
type: '3',
parentID: '0'
},
{
id: '5',
title: 'Child in Level Two - A',
type: '2',
parentID: '1',
},
{
id: '6',
title: 'Child in Level Three - A',
type: '2',
parentID: '5',
},
{
id: '7',
title: 'Child in Level Two - B',
type: '2',
parentId: '1'
},
{
id: '8',
title: 'Child in Level Three - B',
type: '2',
parentID: '5',
}
];
Once processed it needs to look like below:
var AfterModifiedObj = [
{
id: '1',
title: 'Awesome Group',
type: '1',
parentID: '0',
groups: [
{
id: '5',
title: 'Child in Level Two - A',
type: '2',
parentID: '1',
groups: [
{
id: '6',
title: 'Child in Level Three - A',
type: '2',
parentID: '5',
},
{
id: '8',
title: 'Child in Level Three - B',
type: '2',
parentID: '5',
}
]
},
{
id: '7',
title: 'Child in Level Two - B',
type: '2',
parentID: '1'
}
]
},
{
id: '2',
title: 'Rockers',
type: '2',
parentID: '0'
},
{
id: '3',
title: 'Dazzlers',
type: '3',
parentID: '0'
},
{
id: '4',
title: 'Rock-n-Rolla',
type: '3',
parentID: '0'
},
];
Some time ago, I needed a similar function which I slightly adapted to meet your requirements. I commented it and hope that helps. Note in beforeModifiedObj element with ID 7 has a typo in parentID!
function findAndInsert(searchArray, toInsert) {
// We use an internal function in order to avoid that a developer has to pass an additional
// value to findAndInsert
function findAndInsertAcc(searchArray, toInsert, isTopLevel) {
var wasParentFound = false;
for (var i = 0; i < searchArray.length; i++) {
// Item was found => insert and create groups attribute
if (searchArray[i].id == toInsert.parentID) {
if (searchArray[i].groups === undefined) {
searchArray[i].groups = [];
}
searchArray[i].groups.push(toInsert);
wasParentFound = true;
break;
} else if ('groups' in searchArray[i]) {
// Recursively continue the search in the groups property
wasParentFound = findAndInsertAcc(searchArray[i].groups, toInsert, false);
if (wasParentFound) {
break;
}
}
}
// Insert into the top-level array if no item was found and we're in the top level
if (!wasParentFound && isTopLevel) {
searchArray.push(toInsert);
}
return wasParentFound;
}
findAndInsertAcc(searchArray, toInsert, true);
}
var afterModifiedObj = [];
beforeModifiedObj.forEach(function(next) {
findAndInsert(afterModifiedObj, next);
});

Use lodash to group array into tree "children" structure

Using lodash, I need to convert the following array:
[{
text: 'apple',
type: 'fruit'
}, {
text: 'pear',
type: 'fruit',
}, {
text: 'potato',
type: 'vegetable'
}, {
text: 'water',
type: 'beverage'
}]
Into the following format:
[{
text: 'fruit',
children: [{
text: 'apple',
type: 'fruit'
}, {
text: 'pear',
type: 'fruit'
}]
}, {
text: 'vegetable',
children: [{
text: 'potato',
type: 'vegetable'
}]
}, {
text: 'beverage',
children: [{
text: 'water',
type: 'beverage'
}]
}]
I've attempted to chain lodash methods like groupBy and transform, but am having a hard time getting to the resulting format I require.
Here's a skeleton of what direction I was heading:
_(arr).groupBy('type').transform(function(result, obj, type) {
return result.push({
name: type,
children: obj
});
}).value();
The issue I'm running into is groupBy turns my array into an Object, so I can no longer simply push onto an Array. Being relatively knew with lodash (around 4 or 5 months of experience), I wanted to see if others have already tackled such a requirement.
Use _.reduce() instead of transform, because it lets you state the end product format:
var arr = [{
text: 'apple',
type: 'fruit'
}, {
text: 'pear',
type: 'fruit',
}, {
text: 'potato',
type: 'vegetable'
}, {
text: 'water',
type: 'beverage'
}];
var results = _(arr)
.groupBy('type')
.reduce(function(array, children, key) {
array.push({
text: key,
children: children
});
return array;
}, []);
console.log(results);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.js"></script>

Categories