Related
I'm currently making a nav bar.
I create an array storing the info of the tabs in the nav bar.
tabs: [
{ name: 'All',id: "dash.courses.all", to: 'all', current: false },
{ name: 'Self-paced Learning',id: "dash.courses.oneToOneClasses", to: 'selfPacedLearningComp', current: false },
{ name: '30 Days Challenge',id: "dash.courses.selfPacedLearningComp", to: 'thirtyDaysChallenge', current: false },
{ name: 'Group Classes',id: "dash.courses.groupClasses", to: 'groupClasses', current: false },
{ name: '1-to-1 Classes',to: "dash.courses.thirtyDaysChallenge", to: 'oneToOneClasses', current: false },
]
When a new route is clicked it updates the newly clicked tab to allow the current property to be true.
How would you change the previous nav item to false. As currently they all change to true one by one as they are clicked.
I think if I store a value as previous
setCurrent(tab)
{
let newArr = this.tabs.map(obj => {
if (obj.id === tab) {
return {...obj, current: true};
}
return obj;
})
this.tabs = newArr
console.log(newArr)
}
},
This is what i've got atm, it has to go around 3 routes ahead till the one before vanishes...
<script>
export default {
components: {
},
data()
{
return {
prevTab: null,
tabs: [
{ name: 'All',id: "dash.courses.all", to: 'all', current: false },
{ name: 'Self-paced Learning',id: "dash.courses.oneToOneClasses", to: 'selfPacedLearningComp', current: false },
{ name: '30 Days Challenge',id: "dash.courses.selfPacedLearningComp", to: 'thirtyDaysChallenge', current: false },
{ name: 'Group Classes',id: "dash.courses.groupClasses", to: 'groupClasses', current: false },
{ name: '1-to-1 Classes',to: "dash.courses.thirtyDaysChallenge", to: 'oneToOneClasses', current: false },
]
}
},
methods: {
setCurrent(tab)
{
this.prevTab = this.$route.name
let newArr = this.tabs.map(obj => {
if (obj.id === tab) {
return {...obj, current: true};
}
if(obj.id === this.prevTab) {
return {...obj, current: false}
}
return obj;
})
console.log('previous ',this.prevTab)
console.log('route name', this.$route.name)
this.tabs = newArr
}
},
mounted()
{
this.prevTab = this.$route.name
const newArr = this.tabs.map(obj => {
if (obj.id === this.$route.name) {
return {...obj, current: true};
}
return obj;
});
this.tabs = newArr
}
}
Create a watcher on the route name
watch: {
"$route.name": {
handler(routeName) {
this.tabs.forEach((tab) => (tab.current = routeName === tab.name));
},
// force eager callback execution
immediate: true,
},
}
Usually you can just use the watcher routeName value above to run whatever side effect you want but if tracking current on each tab is really necessary the above code will get the job done.
Introduction
I am implementing a method which inserts posts to the respective users posts lists in my map, sorted by date (recent posts first).
This is how I am structuring my data:
state = {
userId: {
posts: [
{ // object returned from my feeds algorithm in the server side
id,
userData: {
id,
},
date,
},
... more posts ...
],
},
... more users ...
}
In my algorithm, I just need to insert all the posts that are inside a given list
[
{ id: "post1", { userData: { id: "alex" }, date },
{ id: "post2", { userData: { id: "sara" }, date }
]
in the posts list of each respective user.
Problem
I also need to avoid inserting posts that already exists in my state, and I can't find a simple way to do it optimally.
Current code
This is my current implementation. I feel that this can be done easier and faster. Any help?
/*
Algorithm
*/
function addContents(state, contents, contentType, cached) {
const newState = state;
contents.forEach((content) => {
const { userData: { id: userId } } = content;
const prevUserState = state.get(userId);
const prevContents = prevUserState?.[contentType] ?? [];
const newContents = prevContents;
// TODO - Avoid inserting if already exists in prevContents! (check by **id**)
let inserted = false;
for (const [index, prevContent] of prevContents.entries()) {
// Replace
if (content.id === prevContent.id) {
newContents[index] = content;
inserted = true;
break;
}
// Insert in the correct order
if(content.date >= prevContent.date) {
newContents.splice(index, 0, content);
inserted = true;
break;
}
}
if (!inserted) {
newContents.push(content);
}
newState.set([
userId,
{
...prevUserState,
[contentType]: newContents
}
]);
});
// if(isEqual(state, newState)) return state; (deep compare to avoid re-renderizations because of state update)
return new Map([...newState]);
}
/*
Test
*/
(() => {
// State
const state = new Map([]);
// User ALEX
const userId1 = "alex";
const userPosts1 = [ // already sorted by date
{
id: "78q78w0w0",
userData: {
id: userId1,
},
date: new Date("10/26/1999 00:00:01")
},
{
id: "92uwdq092",
userData: {
id: userId1,
},
date: new Date("10/26/1999 00:00:00")
}
];
state.set(userId1, { posts: userPosts1 });
// User SARA
const userId2 = "sara";
const userPosts2 = [ // already sorted by date
{
id: "iipzxx115",
userData: {
id: userId2,
},
date: new Date("12/25/2003 03:30:10")
},
{
id: "Wxrr22232",
userData: {
id: userId2,
},
date: new Date("01/01/2000 17:44:41")
}
];
state.set(userId2, { posts: userPosts2 });
const newPosts = [
{
id: "OLDEST FOR ALEX!",
userData: {
id: userId1
},
date: new Date("10/25/1999 23:59:59")
},
{
id: "NEWEST FOR SARA!",
userData: {
id: userId2
},
date: new Date("01/05/2010 22:22:22")
},
{
id: "OLDEST FOR SARA!",
userData: {
id: userId2
},
date: new Date("10/25/1999 23:59:59")
}
]
addContents(state, newPosts, "posts");
console.log(state.get(userId1))
console.log(state.get(userId2))
})();
Note: As this method is implemented in a React's reducer, to manage complex states, I am returning a new Map, after deep comparing the previous and the new state, to produce UI re-renderizations.
UPDATE
I have implemented another version where I do what I need, but maybe, it can be more optimized.
function addContents(state, contents, contentType, cached) {
const newState = state;
const exists = {}; // optimization
for (const content of contents) {
const {
userData: { id: userId },
} = content;
const prevUserState = state.get(userId);
const prevContents = prevUserState?.[contentType] ?? [];
const newContents = prevContents;
if (cached) {
if (!exists[userId]) {
exists[userId] = prevContents.reduce((map, content) => {
map[content.id] = true;
return map;
}, {});
}
// Avoid inserting if necessary
if (exists[userId][content.id]) {
break;
}
}
// Insert the new content in the user's content list
console.log(`Inserting ${content.id}`);
let inserted = false;
for (const [index, prevContent] of prevContents.entries()) {
// Replace
if (content.id === prevContent.id) {
newContents[index] = content;
inserted = true;
break;
}
// Insert in the correct order
if(content.date >= prevContent.date) {
newContents.splice(index, 0, content);
inserted = true;
break;
}
}
if (!inserted) {
newContents.push(content);
}
newState.set([
userId,
{
...prevUserState,
[contentType]: newContents
}
]);
}
// if (isEqual(state, newState)) return state;
return new Map([...newState]);
}
/*
Test
*/
(() => {
// State
let state = new Map([]);
// User ALEX
const userId1 = "alex";
const userPosts1 = [ // already sorted by date
{
id: "78q78w0w0",
userData: {
id: userId1,
},
date: new Date("10/26/1999 00:00:01")
},
{
id: "92uwdq092",
userData: {
id: userId1,
},
date: new Date("10/26/1999 00:00:00")
}
];
state.set(userId1, { posts: userPosts1 });
// User SARA
const userId2 = "sara";
const userPosts2 = [ // already sorted by date
{
id: "iipzxx115",
userData: {
id: userId2,
},
date: new Date("12/25/2003 03:30:10")
},
{
id: "Wxrr22232",
userData: {
id: userId2,
},
date: new Date("01/01/2000 17:44:41")
}
];
state.set(userId2, { posts: userPosts2 });
const newPosts = [
{
id: "OLDEST FOR ALEX!",
userData: {
id: userId1
},
date: new Date("10/25/1999 23:59:59")
},
{
id: "NEWEST FOR SARA!",
userData: {
id: userId2
},
date: new Date("01/05/2010 22:22:22")
},
{
id: "OLDEST FOR SARA!",
userData: {
id: userId2
},
date: new Date("10/25/1999 23:59:59")
}
]
state = addContents(state, newPosts, "posts");
console.log(state.get(userId1))
console.log(state.get(userId2))
/*
Insert again!
*/
state = addContents(state, newPosts, "posts", true);
})();
use an object instead of an array:
This is the same concept of the normalizr library for redux: https://github.com/paularmstrong/normalizr
state = {
[user1Id]: {
posts: {
[post1Id]: {
id,
userData: {
id,
},
date,
},
[post2Id]: {
id,
userData: {
id,
},
date,
},
... more posts ...
},
},
... more users ...
}
This way you can easily access the object you want by its Id and check whether it exists or not just doing: if(state[23].posts[12])
if you need to iterate the users or a user posts use
object.keys(state).map(userId => ...)
or
object.keys(state[23].posts).map(postId => ...)
INSERT/UPDATE:
state[23].posts[newId]: { ...newPost}
I'm not able to follow what you are doing but I think this is what you are after.
You can do it to a oneline very easy.
newdata = [{ id: "post1", { userData: { id: "alex" }, date }]
if(!oldstates.find(d =>
d.id === newdata.id &&
d.userData.id === newdata.userData.id &&
d.date === newdata.date
)) {
oldstates.push(newdata)
}
// oneliner
if(!oldstates.find(d => d.id === newdata.id && d.userData.id === newdata.userData.id && d.date === newdata.date )) oldstates.push(newdata)
Is there a way to only display items on bootstrap-vue b-tables when the user has a filter applied (entered a value into the input)? E.g. if "filteredItems" does not exist, display nothing? This is mainly to prevent my table from rendering all rows (>2k rows) and impeding performance.
jsfiddle with b-table: https://jsfiddle.net/asc82spc/
const template = `
<table :id="id || null"
role="grid"
:aria-busy="isBusy ? 'true' : 'false'"
:class="tableClass"
>
<thead :class="headVariant ? ('thead-' + headVariant) : ''">
<tr role="row">
<th v-for="field,key in fields"
#click="headClick($event,field,key)"
#keydown.enter="headClick($event,field,key)"
#keydown.space.prevent="headClick($event,field,key)"
:class="fieldClass(field,key)"
:aria-label="field.sortable ? (sortDesc ? labelSortAsc : labelSortDesc) : null"
:aria-sort="(field.sortable && sortBy === key) ? (sortDesc ? 'descending' : 'ascending') : null"
:tabindex="field.sortable?'0':null"
v-html="field.label"
></th>
</tr>
</thead>
<tfoot v-if="footClone" :class="footVariant ? ('thead-' + footVariant) : ''">
<tr role="row">
<th v-for="field,key in fields"
#click="headClick($event,field,key)"
#keydown.enter="headClick($event,field,key)"
#keydown.space.prevent="headClick($event,field,key)"
:key="key"
:class="fieldClass(field,key)"
:aria-label="field.sortable ? ((sortDesc) ? labelSortAsc : labelSortDesc) : null"
:aria-sort="(field.sortable && sortBy === key) ? (sortDesc ? 'descending' : 'ascending') : null"
:tabindex="field.sortable?'0':null"
v-html="field.label"
></th>
</tr>
</tfoot>
<tbody>
<tr v-for="(item,index) in _items"
role="row"
:key="items_key"
:class="rowClass(item)"
#click="rowClicked($event,item,index)"
>
<td v-for="(field,key) in fields" :class="cellClass(field)">
<slot :name="key" :value="item[key]" :item="item" :index="index">{{item[key]}}</slot>
</td>
</tr>
<tr v-if="showEmpty && _items.length === 0" role="row">
<td :colspan="Object.keys(fields).length">
<div v-if="filter" role="alert" aria-live="polite">
<slot name="emptyfiltered">
<div class="text-center" v-html="emptyFilteredText"></div>
</slot>
</div>
<div v-else role="alert" aria-live="polite">
<slot name="empty">
<div class="text-center" v-html="emptyText"></div>
</slot>
</div>
</td>
</tr>
</tbody>
</table>
`;
const toString = v => {
if (!v) {
return '';
}
if (v instanceof Object) {
return Object.keys(v).map(k => toString(v[k])).join(' ');
}
return String(v);
};
const recToString = v => {
if (!(v instanceof Object)) {
return '';
}
// Exclude these fields from record stringification
const exclude = {
state: true,
_rowVariant: true
};
return toString(Object.keys(v).filter(k => !exclude[k]).reduce((o, k) => {
o[k] = v[k];
return o;
}, {}));
};
const defaultSortCompare = (a, b, sortBy) => {
return toString(a[sortBy]).localeCompare(toString(b[sortBy]), undefined, {
numeric: true
});
};
const bTable = {
template: template,
data() {
return {
sortBy: null,
sortDesc: true,
isBusy: false,
localItems: null
};
},
props: {
id: {
type: String,
default: ''
},
items: {
type: Array,
default: () => []
},
fields: {
type: Object,
default: () => {
return {};
}
},
striped: {
type: Boolean,
default: false
},
bordered: {
type: Boolean,
default: false
},
inverse: {
type: Boolean,
default: false
},
hover: {
type: Boolean,
default: false
},
small: {
type: Boolean,
default: false
},
responsive: {
type: Boolean,
default: false
},
headVariant: {
type: String,
default: ''
},
footVariant: {
type: String,
default: ''
},
perPage: {
type: Number,
default: null
},
items_key: {
type: String,
default: null
},
currentPage: {
type: Number,
default: 1
},
filter: {
type: [String, RegExp, Function],
default: null
},
sortCompare: {
type: Function,
default: null
},
itemsProvider: {
type: Function,
default: null
},
noProviderPaging: {
type: Boolean,
default: false
},
noProviderSorting: {
type: Boolean,
default: false
},
noProviderFiltering: {
type: Boolean,
default: false
},
value: {
type: Array,
default: () => []
},
footClone: {
type: Boolean,
default: false
},
labelSortAsc: {
type: String,
default: 'Click to sort Ascending'
},
labelSortDesc: {
type: String,
default: 'Click to sort Descending'
},
showEmpty: {
type: Boolean,
default: false
},
emptyText: {
type: String,
default: 'There are no records to show'
},
emptyFilteredText: {
type: String,
default: 'There are no records matching your request'
}
},
watch: {
items(newVal, oldVal) {
console.log('items.watch');
if (oldVal === newVal) {
return;
}
this.localItems = this.items;
},
sortDesc(newVal, oldVal) {
console.log('watch sortDesc:', newVal, oldVal);
if (!this.noProviderSorting) {
this.updater(newVal, oldVal);
}
},
sortBy(newVal, oldVal) {
console.log('watch sortBy:', newVal, oldVal);
if (!this.noProviderSorting) {
this.updater(newVal, oldVal);
}
},
perPage(newVal, oldVal) {
console.log('watch perPage:', newVal, oldVal);
if (!this.noProviderPaging) {
this.updater(newVal, oldVal);
}
},
currentPage(newVal, oldVal) {
console.log('watch currentPage:', newVal, oldVal);
if (!this.noProviderPaging) {
this.updater(newVal, oldVal);
}
},
filter(newVal, oldVal) {
console.log('watch filter:', newVal, oldVal);
if (!this.noProviderFiltering) {
this.updater(newVal, oldVal);
}
},
localItems(newVal, oldVal) {
console.log('localItems updated');
}
},
computed: {
tableClass() {
return [
'table',
this.striped ? 'table-striped' : '',
this.hover ? 'table-hover' : '',
this.inverse ? 'table-inverse' : '',
this.bordered ? 'table-bordered' : '',
this.responsive ? 'table-responsive' : '',
this.small ? 'table-sm' : ''
];
},
_items() {
if (!this.localItems) {
if (this.itemsProvider) {
this.updater(1,2);
return this.items || [];
} else {
this.localItems = this.items || [];
}
}
let items = this.localItems.slice();
// Apply local filter
if (this.filter && !(this.itemsProvider && !this.noProviderFiltering)) {
if (this.filter instanceof Function) {
items = items.filter(this.filter);
} else {
let regex;
if (this.filter instanceof RegExp) {
regex = this.filter;
} else {
regex = new RegExp('.*' + this.filter + '.*', 'ig');
}
items = items.filter(item => {
const test = regex.test(recToString(item));
regex.lastIndex = 0;
return test;
});
}
}
// Apply local Sort
const sortCompare = this.sortCompare || defaultSortCompare;
if (this.sortBy && !(this.itemsProvider && !this.noProviderSorting)) {
console.log('b-table sorting...');
items = items.sort((a, b) => {
const r = sortCompare(a, b, this.sortBy);
return this.sortDesc ? r : r * -1;
});
}
// Apply local pagination
if (this.perPage && !(this.itemsProvider && !this.noProviderPaging)) {
items = items.slice((this.currentPage - 1) * this.perPage, this.currentPage * this.perPage);
}
// Clear busy state
this.$nextTick(() => {
this.isBusy = false;
});
// Update the value model with the filtered/sorted/paginated data set
this.$emit('input', items);
return items;
}
},
methods: {
fieldClass(field, key) {
return [
field.sortable ? 'sorting' : '',
(field.sortable && this.sortBy === key) ? 'sorting_' + (this.sortDesc ? 'desc' : 'asc') : '',
field.variant ? ('table-' + field.variant) : '',
field.class ? field.class : ''
];
},
cellClass(field) {
field.variant ? ('table-' + field.variant) : '',
field.class ? field.class : ''
},
rowClass(item) {
// Prefer item._rowVariant over deprecated item.state
const variant = item._rowVariant || item.state || null;
return [
variant ? ('table-' + variant) : ''
];
},
rowClicked(e, item, index) {
if (this.isBusy) {
e.preventDefault();
e.stopPropagation();
return;
}
this.$emit('row-clicked', item, index);
},
headClick(e, field, key) {
if (this.isBusy) {
e.preventDefault();
e.stopPropagation();
return;
}
if (!field.sortable) {
this.sortBy = null;
} else {
if (key === this.sortBy) {
this.sortDesc = !this.sortDesc;
} else {
this.sortDesc = true;
}
this.sortBy = key;
}
this.$emit('head-clicked', key, this.sortDesc);
},
updater(a,b) {
// #TODO: add providerDebounce
if (a === b || !this.itemsProvider || this.isBusy) {
return;
}
// Set busy state
this.isBusy = true;
this.$nextTick(() => {
// If async, we just keep localItems as is, and awaite provider callback
const items = this.itemsProvider(this);
if (items) {
this.localItems = items.slice();
}
});
},
providerCallback(data) {
this.localItems = data ? data.slice() : [];
}
}
};
new Vue({
el: '#app',
components: {bTable},
data: {
fields: {
name: {
label: 'Person Full name',
sortable: true
},
age: {
label: 'Person age',
sortable: true
},
isActive: {
label: 'is Active'
},
actions: {
label: 'Actions'
}
},
currentPage: 1,
perPage: 5,
filter: null,
async: true
},
methods: {
details(item) {
alert(JSON.stringify(item));
},
provider(ctx) {
console.log('provider called', ctx);
let items = [{
isActive: true,
age: 40,
name: {
first: 'Dickerson',
last: 'Macdonald'
}
}, {
isActive: false,
age: 21,
name: {
first: 'Larsen',
last: 'Shaw'
}
}, {
isActive: false,
age: 9,
state: 'success',
name: {
first: 'Minni',
last: 'Navarro'
}
}, {
isActive: false,
age: 102,
name: {
first: 'Woodrow',
last: 'Wilson'
}
}, {
isActive: true,
age: 38,
name: {
first: 'Jami',
last: 'Carney'
}
}, {
isActive: false,
age: 42,
name: {
first: 'Justin',
last: 'Truedeau'
}
}, {
isActive: true,
age: 72,
name: {
first: 'Dickerson',
last: 'Macdonald Sr.'
}
}, {
isActive: false,
age: 12,
name: {
first: 'Larsen',
last: 'Shaw Jr.'
}
}, {
isActive: false,
age: 26,
name: {
first: 'Mitzi',
last: 'Navarro'
}
}, {
isActive: false,
age: 22,
name: {
first: 'Geneva',
last: 'Wilson'
}
}, {
isActive: true,
age: 38,
name: {
first: 'Janice',
last: 'Carney'
}
}, {
isActive: false,
age: 27,
name: {
first: 'Essie',
last: 'Dunlap'
}
}];
if (this.filter) {
const regex = new RegExp('.*' + this.filter + '.*', 'ig');
items = items.filter(item => {
const test = regex.test(recToString(item));
//regex.lastIndex = 0;
return test;
});
}
if(ctx) {
if (ctx.sortBy) {
items = items.sort((a, b) => {
const r = defaultSortCompare(a, b, ctx.sortBy);
return ctx.sortDesc ? r : r * -1;
});
}
items = items.slice((this.currentPage - 1) * this.perPage, this.currentPage * this.perPage);
if (this.async) {
// Emulate async request
const p = new Promise(resolve => setTimeout(resolve, 1000));
p.then(() => {
ctx.providerCallback(items);
});
} else {
// Non Async
return items;
}
} else {
// Our own app is requesting total # rows
return items;
}
}
}
});
#app {
padding: 20px;
height: 500px;
}
html,body { font-size: 14px;}
table[aria-busy="false"] {
opacity: 1;
}
table[aria-busy="true"] {
opacity: .5;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="justify-content-center my-1 row">
<b-form-fieldset horizontal label-text-align="right" label="Provider:" class="col-4" :label-size="4">
<b-form-select class="form-control" :options="[{text:'Async',value:true},{text:'Sync',value:false}]" v-model="async">
</b-form-select>
</b-form-fieldset>
<b-form-fieldset horizontal label-text-align="right" label="Page Size:" class="col-4" :label-size="6">
<b-form-select class="form-control" :options="[{text:5,value:5},{text:10,value:10},{text:15,value:15}]" v-model="perPage">
</b-form-select>
</b-form-fieldset>
<b-form-fieldset label-text-align="right" horizontal label="Filter:" class="col-4" :label-size="2">
<b-form-input v-model="filter" placeholder="Type to Search"></b-form-input>
</b-form-fieldset>
</div>
<!-- Main table element -->
<!-- :current-page="currentPage" :per-page="perPage" :filter="filter" no-provider-filtering -->
<b-table striped hover head-variant="inverse" :items-provider="provider" :fields="fields" :filter="filter" :current-page="currentPage" :per-page="perPage" show-empty>
<template slot="name" scope="item">
{{item.value.first}} {{item.value.last}}
</template>
<template slot="isActive" scope="item">
{{item.value?'Yes :)':'No :('}}
</template>
<template slot="actions" scope="item">
<b-btn size="sm" #click="details(item.item)">Details</b-btn>
</template>
</b-table>
<div class="justify-content-center row my-1">
<b-pagination size="md" :total-rows="provider(null).length" :per-page="perPage" v-model="currentPage" />
</div>
</div>
Use a v-if on the table to check if filter has data. https://jsfiddle.net/Lsa5qkbt/
<b-table striped hover v-if="filter" head-variant="inverse" :items-provider="provider" :fields="fields" :filter="filter" :current-page="currentPage" :per-page="perPage" show-empty>
v-if creates/destroys the content when the conditional is met/unmet, so no resources are used until the conditional is met. https://v2.vuejs.org/v2/guide/conditional.html#v-if-vs-v-show
I have this problem where I cant really figure out how to implement this in code. I think that I will have to use to power of recursion to do this, but I am really struggling with it.
I have this object (simplified for this question), where the nested depths can be unknown.
const allValues = {
features: {
title: 'features',
description: 'some features',
fields: {
featureA: false,
featureB: true,
featureC: true
}
},
otherthings: {
title: 'otherthings',
description: 'otherthingsdescription',
fields: {
nestedotherthings: {
title: 'nestedotherthings',
description: 'nestedotherthingsdescription',
fields: {
againnested: {
title: 'againsnested',
description: 'againsnested',
fields: {
finallynestedvalue: 1
}
},
againnested2: {
title: 'againsnested2',
description: 'againsnested2',
fields: {
finallynestedvalue: 200
}
}
}
}
}
}
}
I want to run this object through some code and get an output like this:
const expected_output = {
features: {
featureA: false,
featureB: true,
featureC: true
},
othertings: {
nestedotherthings: {
againnested: {
finallynestedvalue: 1
},
againnested2: {
finallynestedvalue: 2
}
}
}
}
What I have tried:
const output = _(allValues).mapValues((value, key) => flattenFields({}, value, key)).value()
function flattenFields(parent, current, key) {
if (!current || !current.fields) {
return current
}
return {
...parent,
[key]: _(current.fields).mapValues((value, key) => flattenFields(current, value, key)).value()
}
}
I hope someone can help me with this and explain what I am doing wrong.
You could take a recursive approach by checking the values and if an object take the fields property or the value. Then rebuild a new object.
function convert(object) {
return Object.fromEntries(Object
.entries(object)
.map(([k, v]) => [k, v && typeof v === 'object' ? convert(v.fields) : v])
);
}
var data = { features: { title: 'features', description: 'some features', fields: { featureA: false, featureB: true, featureC: true } }, otherthings: { title: 'otherthings', description: 'otherthingsdescription', fields: { nestedotherthings: { title: 'nestedotherthings', description: 'nestedotherthingsdescription', fields: { againnested: { title: 'againsnested', description: 'againsnested', fields: { finallynestedvalue: 1 } }, againnested2: { title: 'againsnested2', description: 'againsnested2', fields: { finallynestedvalue: 200 } } } } } } },
result = convert(data);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
In my angular application i am having the data as follows,
forEachArrayOne = [
{ id: 1, name: "userOne" },
{ id: 2, name: "userTwo" },
{ id: 3, name: "userThree" }
]
forEachArrayTwo = [
{ id: 1, name: "userFour" },
{ id: 2, name: "userFive" },
{ id: 3, name: "userSix" }
]
newObj: any = {};
ngOnInit() {
this.forEachArrayOne.forEach(element => {
this.newObj = { titleOne: "objectOne", dataOne: this.forEachArrayOne };
})
this.forEachArrayTwo.forEach(element => {
this.newObj = { titleTwo: "objectTwo", dataTwo: this.forEachArrayTwo };
})
console.log({ ...this.newObj, ...this.newObj });
}
In my real application, the above is the structure so kindly help me to achieve the expected result in the same way..
The working demo https://stackblitz.com/edit/angular-gyched which has the above structure.
Here console.log(this.newObj) gives the last object,
titleTwo: "ObjectTwo",
dataTwo:
[
{ id: 1, name: "userFour" },
{ id: 2, name: "userFive" },
{ id: 3, name: "userSix" }
]
but i want to combine both and need the result exactly like the below..
{
titleOne: "objectOne",
dataOne:
[
{ id: 1, name: "userOne" },
{ id: 2, name: "userTwo" },
{ id: 3, name: "userThree" }
],
titleTwo: "ObjectTwo",
dataTwo:
[
{ id: 1, name: "userFour" },
{ id: 2, name: "userFive" },
{ id: 3, name: "userSix" }
]
}
Kindly help me to achieve the above result.. If i am wrong in anywhere kindly correct with the working example please..
You're assigning both values to this.newObj, so it just overwrites the first object.
Also, there is no need for your loop. It doesn't add anything.
Instead, you can do:
this.newObjA = { titleOne: "objectOne", dataOne: this.forEachArrayOne };
this.newObjB = { titleTwo: "objectTwo", dataTwo: this.forEachArrayTwo };
console.log({ ...this.newObjA, ...this.newObjB });
**
EDIT **
Having spoken to you regarding your requirements, I can see a different solution.
Before calling componentData, you need to make sure you have the full data. To do this, we can use forkJoin to join the benchmark requests, and the project requests into one Observable. We can then subscribe to that Observable to get the results for both.
The code would look something like this:
createComponent() {
let benchmarks, projects;
let form = this.productBenchMarkingForm[0];
if (form.benchmarking && form.project) {
benchmarks = form.benchmarking.filter(x => x.optionsUrl)
.map(element => this.getOptions(element));
projects = form.project.filter(x => x.optionsUrl)
.map(element => this.getOptions(element));
forkJoin(
forkJoin(benchmarks), // Join all the benchmark requests into 1 Observable
forkJoin(projects) // Join all the project requests into 1 Observable
).subscribe(res => {
this.componentData({ component: NgiProductComponent, inputs: { config: AppConfig, injectData: { action: "add", titleProject: "project", dataProject: this.productBenchMarkingForm[0] } } });
})
}
}
getOptions(element) {
return this.appService.getRest(element.optionsUrl).pipe(
map((res: any) => {
this.dataForOptions = res.data;
element.options = res.data;
return element;
})
)
}
Here is an example in Stackblitz that logs the data to the console