Easy Autocomplete nested Json structure - javascript

I'm having a hard time to structure the list location for items array in order to access name attribute in search.js
Below is the nested JSON structure:
{
menus: [
{
name: "Summer ",
url: "/menus/2",
items: [
{
name: "man o man", //this is what I'm trying to access
url: "/menus/2/items/7"
}
]
]
}
So far I've tried in search.js:
document.addEventListener("turbolinks:load", function() {
$input = $("[data-behavior='autocomplete']")
var options = {
getValue: "name",
url: function(phrase) {
data = "/search.json?q=" + phrase;
return data;
},
categories: [
{
listLocation: "menus",
header: "--<strong>Menus</strong>--",
},
{
listLocation: "items", //this is where I'm having problem with
header: "--<strong>Items</strong>--",
}
],
list: {
onChooseEvent: function() {
var url = $input.getSelectedItemData().url
$input.val("")
Turbolinks.visit(url)
}
}
}
$input.easyAutocomplete(options)
})

Related

Node 'Flatten/Merge' Array of Objects Containing Arrays and Display with forEach in EJS

Apologies for any repeats here, please point me in the direction of a solution if one exists.
Any other time, I seem able to display array data in ejs no problem, but this new flatten function has me stumped.
I have a nested array:
var array =
[{
page: {
results: [{
id: "1234",
type: "page",
title: "Deprecated Spec",
children: {
page: {
results: [{
id: "5678",
type: "page",
title: "Deprecated | Cash Spec"
},
{
id: "9101",
type: "page",
title: "Deprecated | Ledger Spec",
}
],
},
},
},
{
id: "1121",
type: "page",
title: "Work Spec"
}
]
}
}];
And I have a flatten function:
function flatten(arr) {
let flattened = [];
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
flattened = flattened.concat(flatten(arr[i]));
} else {
flattened.push(arr[i]);
}
}
return flattened;
}
I set a const and console.log:
const newArr = (flatten(array))
console.log(newArr);
In the terminal I receive: [ { page: { results: [Array] } } ]
For what it's worth, I've also tried flattening this nested array with these methods, and both return the same results.
const newArr2 = Array.prototype.concat.apply([], array);
const newArr3 = array.flat();
Hmm ok, I figure I can forEach and display the data on the .ejs page, which is set as:
<% newArr.forEach(function (newArr){ %>
<%= newArr.title %>
<% }) %>
But this shows nothing.
I have the res.render functioning properly as I am able to send and display other arrays/data sets.
If I try to "get into the array" with something like this: newArr[0].page.results, or any other variances, they don't work.
What am I missing to see my flattened array (using the recursive function) in both the console.log and on my ejs page?
UPDATED:
Per comments below, here is my desired output for the arrays and objects seen in var array:
[{
page: {
results: [
{
id: "1234",
type: "page",
title: "Deprecated Spec"
},
{
id: "5678",
type: "page",
title: "Deprecated | Cash Spec"
},
{
id: "9101",
type: "page",
title: "Deprecated | Ledger Spec"
},
{
id: "1121",
type: "page",
title: "Work Spec"
}
]
}
}];
Here is a pic of my current console.log
Many Thanks!
You should adapt your code to flatten the array like this to achieve the desired output:
const array = [
{
page: {
results: [
{
id: '1234',
type: 'page',
title: 'Deprecated Spec',
children: {
page: {
results: [
{
id: '5678',
type: 'page',
title: 'Deprecated | Cash Spec',
},
{
id: '9101',
type: 'page',
title: 'Deprecated | Ledger Spec',
},
],
},
},
},
{
id: '1121',
type: 'page',
title: 'Work Spec',
},
],
},
},
]
function flatten(arr) {
const flattened = []
for (const { children, ...element } of arr) {
flattened.push(element)
if (children) {
flattened.push(...flatten(children.page.results))
}
}
return flattened
}
const flat = [{ page: { results: flatten(array[0].page.results) } }]
console.log(flat)
console.log(flat[0].page.results)
// [
// {
// "page": {
// "results": [
// {
// "id": "1234",
// "type": "page",
// "title": "Deprecated Spec"
// },
// {
// "id": "5678",
// "type": "page",
// "title": "Deprecated | Cash Spec"
// },
// {
// "id": "9101",
// "type": "page",
// "title": "Deprecated | Ledger Spec"
// },
// {
// "id": "1121",
// "type": "page",
// "title": "Work Spec"
// }
// ]
// }
// }
// ]

Object gets duplicated inside array

This is the result I want to achieve
dataset: [
dataset: [
{
seriesname: "",
data: [
{
value: "123",
},
{
value: "123",
},
]
},
]
]
My problem right now is that the second dataset gets duplicated.
This is how I am setting it (val is an integer and allYears is an array of integers):
this.grphColumn.dataSource.dataset[0].dataset = this.allYears.map(el => {
return {
seriesname: "Planned",
data: [{value: val}, {value: val}]
}
});
How can I make it so the dataset doesn't get duplicated?
You have to map the values separately, if you dont want the seriesName to be Repeated..
const yearsMap = this.allYears.map((el) => { return { value: el } });
this.grphColumn.dataSource.dataset[0].dataset = {
seriesname: "Planned",
data: yearsMap
}

Creating new data object set from multiple nested arrays

I've have a complex data structure with multiple nested arrays in place.
Below is the current structure
var contentData = {
data: {
content: [
{
type: "column",
sections: [
{
sub: [
{
type: "heading-1",
text: "Heading Text"
}
]
}
]
},
{
type: "acc-item",
sections: [
{
sub: [
{
type: "heading-1",
text: "Heading Text"
},
{
type: "ordered-item",
text: "Item 1"
},
{
type: "unordered-item",
text: "Item 2"
}
]
}
]
},
{
type: "acc-item",
sections: [
{
sub: [
{
type: "heading-1",
text: "Heading Text 2"
}
]
}
]
}
]
}
}
So What I wanted is,
I wanted to group all the ordered-item & unordered-item into a new object like {type: 'list', items:[all list items]}.
I need to extract all items which are inside sub and push it to new object embedded and it should placed in the root level like below,
{type:"acc-item",embedded:[{type:"heading-1",text:"Heading Text 2"}]};
So What I've done so far,
I can able to group acc-item, but not the ordered-item & unordered-item.
So my final expected result should like this,
[{
"type": "column",
"embedded": [
{
"type": "heading-1",
"text": "Heading Text"
}
]
},
{
"type": "acc-group",
"items": [
{
"type": "acc-item",
"embedded": [
{
"type": "heading-1",
"text": "Heading Text"
},
{
"type": "list",
"items": [
{
"type": "ordered-item",
"text": "Item 1"
},
{
"type": "unordered-item",
"text": "Item 2"
}
]
}
]
},
{
"type": "acc-item",
"embedded": [
{
"type": "heading-1",
"text": "Heading Text 2"
}
]
}
]
}]
Below is my code,
var group,contentData={data:{content:[{type:"column",sections:[{sub:[{type:"heading-1",text:"Heading Text"}]}]},{type:"acc-item",sections:[{sub:[{type:"heading-1",text:"Heading Text"},{type:"ordered-item",text:"Item 1"},{type:"unordered-item",text:"Item 2"}]}]},{type:"acc-item",sections:[{sub:[{type:"heading-1",text:"Heading Text 2"}]}]}]}},types=[["list",["ordered-item","unordered-item"]],["accordion",["acc-item"]]];
var result = contentData.data.content.reduce((r, o) => {
var type = (types.find(({ 1: values }) => values.indexOf(o.type) > -1)|| {})[0];
if (!type) {
r.push(o);
group = undefined;
return r;
}
if (!group || group.type !== type) {
group = { type, items: [] };
r.push(group);
}
group.items.push(o);
return r;
}, []);
document.body.innerHTML = '<pre>' + JSON.stringify(result, null, ' ') + '</pre>';
You could store the last items array as well as the last embedded array and use them until a column type is found.
var contentData = { data: { content: [{ type: "column", sections: [{ sub: [{ type: "heading-1", text: "Heading Text" }] }] }, { type: "acc-item", sections: [{ sub: [{ type: "heading-1", text: "Heading Text" }, { type: "ordered-item", text: "Item 1" }, { type: "unordered-item", text: "Item 2" }] }] }, { type: "acc-item", sections: [{ sub: [{ type: "heading-1", text: "Heading Text 2" }] }] }] } },
list = ["ordered-item", "unordered-item"],
lastItems, lastEmbedded,
result = contentData.data.content.reduce((r, { type, sections }) => {
if (type === 'column') {
r.push({ type, embedded: sections.reduce((q, { sub }) => q.concat(sub), []) });
lastItems = undefined;
lastEmbedded = undefined;
return r;
}
if (!lastItems) r.push({ type: "acc-group", items: lastItems = [] });
lastItems.push(...sections.map(({ sub }) => ({
type,
embedded: sub.reduce((q, o) => {
if (list.includes(o.type)) {
if (!lastEmbedded) q.push({ type: 'list', items: lastEmbedded = [] });
lastEmbedded.push(o);
} else {
q.push(o);
lastEmbedded = undefined;
}
return q;
}, [])
})));
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
The Array.prototype and Object.prototype methods are perfect for this kind of thing.
And you're right that this is some complicated kind of logic.
I would suggest that you definitely need some unit tests for this, and try break in to separate pieces.
Here's how I'm thinking I'd do it.
1. Group By the type to create your groups..
I'm actually creating a more generic solution that you've asked for here. That is, I'm not just grouping the 'acc-item', but everything.
I did a quick search for 'array group by javascript' and it gives us this answer which suggests using Array.reduce, so let's do that.
const groupedData = contentData.data.content.reduce((acc, cur) => {
//Check if this indexed array already exists, if not create it.
const currentArray = (acc[`${cur.type}-group`] && acc[`${cur.type}-group`].items) || [];
return {
...acc,
[`${cur.type}-group`]: {
type: `${cur.type}-group`,
items: [...currentArray, cur]
}
}
}, {});
2. Now for each of those items, we need to look at their subs, and group just the list items.
To do this, we basically want to find all the `item -> sections -> sub -> types and filter them into two arrays. A quick google on how to create two arrays using a filter gives me this answer.
First though, we need to flatten that sections-> subs thing, so lets just do that.
function flattenSectionsAndSubs(item) {
return {
type: item.type,
subs: item.sections.reduce((acc, cur) => ([...acc, ...cur.sub]), [])
};
}
And I'll just copy paste that partition function in:
function partition(array, isValid) {
return array.reduce(([pass, fail], elem) => {
return isValid(elem) ? [[...pass, elem], fail] : [pass, [...fail, elem]];
}, [[], []]);
}
const listTypes = ['ordered-item', 'unordered-item'];
function createEmbeddedFromItem(item) {
const [lists, nonLists] = partition(item.subs, (v) => listTypes.includes(v.type);
return {
type: item.type,
embedded: [
...nonLists,
{
type: "list",
items: lists
}
]
}
}
Putting this all together and we get.
const contentData = {
data: {
content: [{
type: "column",
sections: [{
sub: [{
type: "heading-1",
text: "Heading Text"
}]
}]
},
{
type: "acc-item",
sections: [{
sub: [{
type: "heading-1",
text: "Heading Text"
},
{
type: "ordered-item",
text: "Item 1"
},
{
type: "unordered-item",
text: "Item 2"
}
]
}]
},
{
type: "acc-item",
sections: [{
sub: [{
type: "heading-1",
text: "Heading Text 2"
}]
}]
}
]
}
}
function partition(array, isValid) {
return array.reduce(([pass, fail], elem) => {
return isValid(elem) ? [
[...pass, elem], fail
] : [pass, [...fail, elem]];
}, [
[],
[]
]);
}
function flattenSectionsAndSubs(item) {
return {
type: item.type,
subs: item.sections.reduce((acc, cur) => ([...acc, ...cur.sub]), [])
};
}
const listTypes = ['ordered-item', 'unordered-item'];
function createEmbeddedFromItem(item) {
const [lists, nonLists] = partition(item.subs, (v) => listTypes.includes(v.type));
return {
type: item.type,
embedded: [
...nonLists,
{
type: "list",
items: lists
}
]
}
}
const groupedData = contentData.data.content.reduce((acc, cur) => {
//Check if this indexed array already exists, if not create it.
const currentArray = (acc[`${cur.type}-group`] && acc[`${cur.type}-group`].items) || [];
const flattenedItem = flattenSectionsAndSubs(cur);
const embeddedItem = createEmbeddedFromItem(flattenedItem);
return {
...acc,
[`${cur.type}-group`]: {
type: `${cur.type}-group`,
items: [...currentArray, embeddedItem]
}
}
}, {});
console.log(groupedData);
Now this doesn't exactly match what you've asked for - but it should probably work.
You can add your own bits into only add a list item, if the array isn't empty, and to stop the column from being in its own group.
The thing is - tbh it seems like a little bit of a red flag that you would create an array of items that don't having matching structures, which is why I've done it this way.

JS: change params format from hash to array

I have this piece of JS, where I have create action with Ajax:
$(document).ready(function() {
editor = new $.fn.dataTable.Editor( {
table: "#user_groups_table",
template: '#user_groups_form',
display: "details",
idSrc: "id",
ajax: {
create: {
type: 'POST',
url: '/strongbolt/user_groups',
}
},
fields: [ {
name: "name"
}, {
name: "description"
}, {
type: "checkbox",
name: "users[].id",
optionsPair: {
label: 'name',
value: 'id'
}
}, {
type: "checkbox",
name: "roles[].id",
optionsPair: {
label: 'name',
value: 'id'
}
}
]
} );
editor.on( 'preSubmit', function ( e, data, action ) {
if ( action === 'create' ) {
data.strongbolt_user_group = {
"name": data.data[0].name,
"description": data.data[0].description,
"user_ids": data.data[0].users,
"role_ids": data.data[0].roles
};
delete data.data;
}
} );
The last section which starts with editor.on( 'preSubmit', function ( e, data, action ) { basically modifies data before they are passed to server.
At the moment I get my params in terminal like this:
{
"strongbolt_user_group"=>{
"name"=>"Some test group",
"description"=>"Some test description",
"user_ids"=>{"0"=>{"id"=>"3"}, "1"=>{"id"=>"2"}, "2"=>{"id"=>"5"}},
"role_ids"=>{"0"=>{"id"=>"1"}, "1"=>{"id"=>"2"}}
}
}
however I need it to be like this:
{
"strongbolt_user_group"=>{
"name"=>"Some test group",
"description"=>"Some test description",
"user_ids"=>["3", "2", "5"],
"role_ids"=>["1", "2"]
}
}
Basically I need user_ids and role_ids to be array.
How do I modify this in my JS, please? Thank you!
You have to map the array (or array-like object) of objects to an array of their ids:
"user_ids": Array.prototype.map.call(data.data[0].users, function(o) { return o.id; }),
If you are certain that data.data[0].users is an array, then just use map without call like:
"user_ids": data.data[0].users.map(function(o) { return o.id; }),
or even shorter in ES 6's arrow functions:
"user_ids": data.data[0].users.map(o => o.id),
Note: same applies for role_ids.

Declaring dynamic keys in json (javascript, node)

I have an array of objects, each object is similar to:
{ word: 'intentional',
definition: 'done by intention or design',
type: 'adjective',
Synonyms: [ 'conscious', 'deliberate', 'intended', 'knowing', ] }
I am trying to convert the whole array into following json format:
{
"conscious": {
"data": ["done by intention or design"],
"type": "adjective",
"Synonym For": ["intentional"]
},
"deliberate": {
"data": ["done by intention or design"],
"type": "adjective",
"Synonym For": ["intentional"]
},
...
}
This json format is an input to another program, which I do not control.
I am running it on node.js.
How can I declare an object and then loop through the array to fill it as intended?
var obj = { word: 'intentional',
definition: 'done by intention or design',
type: 'adjective',
Synonyms: [ 'conscious', 'deliberate', 'intended', 'knowing' ] },
res = obj.Synonyms.reduce(function(s,a) {
s[a] = { data: [obj.definition], type: obj.type, SynonymFor: [obj.word] };
return s;
}, {});
console.log(res);
var jsonObj = {};
wordArray.forEach((word) => {
word.Synonyms.forEach((synonym) => {
jsonObj[synonym] = {
data: [word.definition],
type: word.type,
'Synonym For': [word.word]
};
})
})

Categories

Resources