get data of a nestet JSONModel into a sap.m.table - model-binding

i'm trying to display a table (sap.m.table) with 2 columns (Roles, User). the table get its data from a JSONModel
model:
{
"roles":[
{
"id":1,
"name":"Administrator",
"permissions":[
{
"id":1,
"permissionUtilPermission":"CREATE_USER",
"description":"Create User",
"name":"Create User"
}
],
"user":[
{
"loginName":"admin1",
"firstName":"John",
"lastName":"Doe",
"id":1,
"active":true
},
{
"loginName":"admin2",
"firstName":"Carmen",
"lastName":"Stiffler",
"id":2,
"active":true
}
.........
]
},
{
"id":2,
"name":"User",
"permissions":[
],
"user":[
{
"loginName":"user1",
"firstName":"Carlos",
"lastName":"Mayer",
"id":3,
"active":true
},
{
"loginName":"user2",
"firstName":"Jonny",
"lastName":"Jefferson",
"id":4,
"active":true
},
.......
]
}
]
}
in the view i create the table with 2 columns and a ColumnListItem as template.
this template contains a sap.m.Text and a sap.m.FlexBox which have a sap.m.Button as item.
In the first column should be displayed the 'name' of the 'roles'.
In the second column should be displayed the FlexBox within Buttons and the text of these buttons should be the names ('loginName') of the roles user
view:
var oTable = new sap.m.Table('mRoleTable', {
width: '75%',
columns: [new sap.m.Column('', {
header: new sap.ui.commons.Label({
text: '{i18n>admin.RoleTableHeaderRole}'
})
}),
new sap.m.Column('', {
header: new sap.ui.commons.Label({
text: '{i18n>admin.RoleTableHeaderUser}'
})
})],
items: new sap.m.ColumnListItem('oRolesItemTemplateId', {
cells: [new sap.m.Text('item1Id', {
text: '{name}'
}),
new sap.m.FlexBox('item2Id', {
items: new sap.m.Button('userBtnId', {
text: '{user}'
}),
})]
})
});
the onInit function of the controller instantiates a new JSONModel and load the data from the json file. after that the model is set to the table and the items are bind to '/roles'
controller:
onInit: function() {
var oView = this.getView();
var oController = this;
var oModel = new sap.ui.model.json.JSONModel();
oModel.loadData('../model/rolemanagement.json');
oModel.attachRequestCompleted(oModel, function(e) {
if (e.getParameters().success) {
console.log('loading successful...');
sap.ui.getCore().setModel(oModel);
var oMTable = sap.ui.getCore().byId('mRoleTable');
oMTable.setModel(oModel);
var oRolesItemTemplate = sap.ui.getCore().byId('oRolesItemTemplateId');
oMTable.bindAggregation("items", "/roles", oRolesItemTemplate);
} else {
console.log('. . . LOADING ERROR');
console.log(e.getParameters().errorobject.statusText);
}
});
}
if i try, it only display's a table like this:
Role User
------------------------------------
Administrator [object Object]
User [object Object]
but what i need is a table like this:
Role User
------------------------------------
Administrator admin1 admin2
User user1 user2
here the same table for a better understanding:
Role User
_______________________________________________
----------------------------------------------
| ------------------------ |<---ColumnListItem
| | |<-|------FlexBox
|Administrator | --------- -------- | |
| | | admin1 || admin2 |<-|--|-------- Buttons (text: '/role/user/loginName')
| | --------- -------- | |
| ------------------------ |
----------------------------------------------
----------------------------------------------
| ------------------------ |<---ColumnListItem
| | |<-|------FlexBox
| User | --------- -------- | |
| | | user1 || user2 |<-|--|-------- Buttons (text: '/role/user/loginName')
| | --------- -------- | |
| ------------------------ |
----------------------------------------------
did someone have a solution for my problem?

The relationship between role and users within that role is one of a parent child.
This implies a list within a list. Would you not consider adopting the parent child template that deals with this scenario.
You would then select the role and then be presented with the users within that role.
Hardcoding {user/0/loginName} instead of {user} would give you the first user (but you probably knew that?)
My personal advice would be to adopt a parent child drill down (i.e. implemented through 2 views where the 2nd view is passed the role selected and it displays the users mapped to that role).
Some other minor points (from a best practice perspective) that I have picked up in my reading and coding: use jQuery.sap.log.info( instead of console.log( and...
for compatibility purposes going forward the OpenUI team are recommending naming models i.e. oMTable.setModel(oModel, "roles") . This will mean adjusting the bindings to include the named model reference {roles>....}.
(Just in case this mattered to you - refer to the API documentation for details.)

now i got a solution which works fine:
controller:
var oView = this.getView();
var oController = this;
//load data for roles
var oModel = new sap.ui.model.json.JSONModel();
oModel.loadData('../model/rolemanagement.json');
oModel.attachRequestCompleted(oModel, function(e) {
if (e.getParameters().success) {
jQuery.sap.log.info('loading roles model successful...');
sap.ui.getCore().setModel(oModel);
var oMTable = sap.ui.getCore().byId('mRoleTable');
oMTable.setModel(oModel);
} else {
jQuery.sap.log.info('. . . LOADING ERROR');
jQuery.sap.log.info(e.getParameters().errorobject.statusText);
}
});
view:
new sap.m.Table('mRoleTable',{
width: '75%',
columns: [
new sap.m.Column({width: '25%', header: new sap.m.Label({text: "Role"})}),
new sap.m.Column({ header: new sap.m.Label({text: "User"})}),
],
items: {
path: "/",
template: new sap.m.ColumnListItem({
cells: [
new sap.m.Text({ text: "{roleName}" }),
new sap.ui.layout.Grid({
content: {
path: 'users',
template: new sap.m.Button('tmpBtn',{
text: '{loginName}',
type: 'Transparent',
layoutData: new sap.ui.layout.GridData({
span: "L2 M4 S5"
})
})
}
})
]
})
}
})

Related

Pagination error on Gatsby JS category and tag pages

I use the awesome paginate plugin for Gatsby to do pagination on archive pages on the site. The plugin connects to the gatsby-node file.js is called there with the help of such code
paginate({
createPage,
items: allMdx.edges,
itemsPerPage: 1,
pathPrefix: '/work',
component: path.resolve('src/templates/work.js')
});
Pagination works great on blog and works pages. I created these pages using the Gatsby Route API, without using the createPage function in gatsby-node. Pagination works great on blog and works pages. I created these pages using the Gatsby Route API, without using the createPage function in gatsby-node.js . But on the pages of tags and categories, I can't enable pagination. These pages were created using createPage. Below I present the code of my gatsby-node.js
const _ = require("lodash")
//const readingTime = require("reading-time")
const { paginate } = require('gatsby-awesome-pagination')
const path = require("path")
const { transliterate } = require('./src/functions/transletter')
const { createFilePath } = require('gatsby-source-filesystem')
/* Create category page */
function dedupeCategories(allMdx) {
const uniqueCategories = new Set()
// Iterate over all articles
allMdx.edges.forEach(({ node }) => {
// Iterate over each category in an article
node.frontmatter.categories.forEach(category => {
uniqueCategories.add(category)
})
})
// Create new array with duplicates removed
return Array.from(uniqueCategories)
}
/* Create tag page */
function dedupeTags(allMdx) {
const uniqueTags = new Set()
// Iterate over all articles
allMdx.edges.forEach(({ node }) => {
// Iterate over each category in an article
node.frontmatter.tags.forEach(tag => {
uniqueTags.add(tag)
})
})
// Create new array with duplicates removed
return Array.from(uniqueTags)
}
exports.createPages = async ({ graphql, actions, reporter }) => {
const { createPage } = actions
// Query markdown files including data from frontmatter
const {
data: { allMdx },
} = await graphql(`
query {
allMdx (filter: {frontmatter: {type: {in: "blog"}}}){
edges {
node {
id
frontmatter {
categories
tags
slug
}
fields {
slug
}
}
}
}
}
`)
// Create array of every category without duplicates
const dedupedCategories = dedupeCategories(allMdx)
// Iterate over categories and create page for each
dedupedCategories.forEach(category => {
reporter.info(`Creating page: blog/category/${category}`)
createPage({
path: `blog/category/${_.kebabCase(transliterate(category))}`,
component: require.resolve("./src/templates/categories.js"),
// Create props for our CategoryList.js component
context: {
category,
// Create an array of ids of articles in this category
ids: allMdx.edges
.filter(({ node }) => {
return node.frontmatter.categories.includes(category)
})
.map(({node}) => node.id),
},
})
})
// Create array of every category without duplicates
const dedupedTags = dedupeTags(allMdx)
// Iterate over categories and create page for each
dedupedTags.forEach(tag => {
reporter.info(`Creating page: blog/tag/${tag}`)
createPage({
path: `blog/tag/${_.kebabCase(transliterate(tag))}`,
component: require.resolve("./src/templates/tags.js"),
// Create props for our CategoryList.js component
context: {
tag,
// Create an array of ids of articles in this category
ids: allMdx.edges
.filter(({ node }) => {
return node.frontmatter.tags.includes(tag)
})
.map(({node}) => node.id),
},
})
})
/* It's creating a new page for each post. */
paginate({
createPage,
items: allMdx.edges,
itemsPerPage: 1,
pathPrefix: '/blog',
component: path.resolve('src/templates/blog.js')
});
/* It's creating a new page for each post. */
paginate({
createPage,
items: allMdx.edges,
itemsPerPage: 1,
pathPrefix: '/work',
component: path.resolve('src/templates/work.js')
});
/* It's creating a new page for each post. */
paginate({
createPage,
items: allMdx.edges,
itemsPerPage: 1,
pathPrefix: '/blog/category',
component: path.resolve('src/templates/categories.js')
});
}
/* Creating a slug for each post. */
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions;
if (node.internal.type === `Mdx`) {
const value = createFilePath({ node, getNode });
createNodeField({
name: 'slug',
node,
value,
});
}
};
/* It's creating a new field in the GraphQL schema called `relatedPosts` that returns an array of Mdx
nodes. */
exports.createResolvers = ({ createResolvers }) => {
const resolvers = {
Mdx: {
relatedPosts: {
type: ['Mdx'],
resolve: (source, args, context, info) => {
return context.nodeModel.runQuery({
query: {
filter: {
id: {
ne: source.id,
},
frontmatter: {
// type: {
// ne: source.frontmatter.type === `work`,
// },
tags: {
in: source.frontmatter.tags,
},
},
},
},
type: 'Mdx',
})
},
},
},
}
createResolvers(resolvers)
}
And this is the template code of my category src/templates/category.js
import React from "react"
import { Link, graphql } from "gatsby"
import Layout from '../components/layout'
import Seo from '../components/seo'
import Pager from '../components/pagination'
const CategoryList = ({ pageContext: { category }, data: { allMdx }, pageContext }) =>
(
<Layout pageTitle={category}>
{
allMdx.edges.map(({ node }) => {
return (
<article key={node.id}>
<h2>
<Link to={`/blog${node.fields.slug}`}>
{node.frontmatter.title}
</Link>
</h2>
<p>Posted: {node.frontmatter.date}</p>
<p>{node.excerpt}</p>
</article>
)
})
}
<Pager pageContext={pageContext} />
</Layout>
)
export const query = graphql`
query CategoryListQuery($ids: [String]!, $limit: Int!, $skip: Int!) {
allMdx (filter: { id: { in: $ids }, frontmatter: {type: {in: "blog"}}}, limit: $limit,
skip: $skip) {
edges {
node {
frontmatter {
title
date(formatString: "MMMM DD, YYYY")
}
fields {
slug
}
id
excerpt
}
}
}
}
`
export const Head = ({ pageContext }) => (
<Seo
title={pageContext.category}
description={`Статьи из категории ${pageContext.category}`}
/>
)
export default CategoryList
The Pager component looks like this
import React from 'react';
import { Link } from 'gatsby';
const Pager = ({ pageContext }) => {
const { previousPagePath, nextPagePath } = pageContext;
return (
<nav style={{ display: 'flex', justifyContent: 'space-between' }}>
<div>
{previousPagePath && (
<Link to={previousPagePath}>
<button>← Newer Posts</button>
</Link>
)}
</div>
<div style={{ justifySelf: 'flex-end' }}>
{nextPagePath && (
<Link to={nextPagePath}>
<button>Older Posts →</button>
</Link>
)}
</div>
</nav>
);
};
export default Pager;
This is errors
ERROR #85920 GRAPHQL
There was an error in your GraphQL query:
Variable "$limit" of required type "Int!" was not provided.
> 1 | query CategoryListQuery($ids: [String]!, $limit: Int!, $skip: Int!) {
| ^
2 | allMdx(
3 | filter: {id: {in: $ids}, frontmatter: {type: {in: "blog"}}}
4 | limit: $limit
5 | skip: $skip
6 | ) {
7 | edges {
8 | node {
9 | frontmatter {
10 | title
11 | date(formatString: "MMMM DD, YYYY")
File path: C:/gatsby/schtml/src/templates/categories.js
Url path: /blog/category/gatsby-js
Plugin: none
Suggestion 1:
If you're not using a page query but a useStaticQuery / StaticQuery you see this error because they currently don't support
variables. To learn more about the limitations of useStaticQuery / StaticQuery, please visit these docs:
https://www.gatsbyjs.com/docs/how-to/querying-data/use-static-query/
https://www.gatsbyjs.com/docs/how-to/querying-data/static-query/
Suggestion 2:
You might have a typo in the variable name "$limit" or you didn't provide the variable via context to this page query. Have a look
at the docs to learn how to add data to context:
https://www.gatsbyjs.com/docs/how-to/querying-data/page-query#how-to-add-query-variables-to-a-page-query
ERROR #85920 GRAPHQL
There was an error in your GraphQL query:
Variable "$skip" of required type "Int!" was not provided.
> 1 | query CategoryListQuery($ids: [String]!, $limit: Int!, $skip: Int!) {
| ^
2 | allMdx(
3 | filter: {id: {in: $ids}, frontmatter: {type: {in: "blog"}}}
4 | limit: $limit
5 | skip: $skip
6 | ) {
7 | edges {
8 | node {
9 | frontmatter {
10 | title
11 | date(formatString: "MMMM DD, YYYY")
File path: C:/gatsby/schtml/src/templates/categories.js
Url path: /blog/category/gatsby-js
Plugin: none
Suggestion 1:
If you're not using a page query but a useStaticQuery / StaticQuery you see this error because they currently don't support
variables. To learn more about the limitations of useStaticQuery / StaticQuery, please visit these docs:
https://www.gatsbyjs.com/docs/how-to/querying-data/use-static-query/
https://www.gatsbyjs.com/docs/how-to/querying-data/static-query/
Suggestion 2:
You might have a typo in the variable name "$skip" or you didn't provide the variable via context to this page query. Have a look at the docs to learn how to add data to context:
https://www.gatsbyjs.com/docs/how-to/querying-data/page-query#how-to-add-query-variables-to-a-page-query
ERROR #85920 GRAPHQL
There was an error in your GraphQL query:
Variable "$limit" of required type "Int!" was not provided.
> 1 | query CategoryListQuery($ids: [String]!, $limit: Int!, $skip: Int!) {
| ^
2 | allMdx(
3 | filter: {id: {in: $ids}, frontmatter: {type: {in: "blog"}}}
4 | limit: $limit
5 | skip: $skip
6 | ) {
7 | edges {
8 | node {
9 | frontmatter {
10 | title
11 | date(formatString: "MMMM DD, YYYY")
File path: C:/gatsby/schtml/src/templates/categories.js
Url path: /blog/category/gatsby-develop
Plugin: none
Suggestion 1:
If you're not using a page query but a useStaticQuery / StaticQuery you see this error because they currently don't support
variables. To learn more about the limitations of useStaticQuery / StaticQuery, please visit these docs:
https://www.gatsbyjs.com/docs/how-to/querying-data/use-static-query/
https://www.gatsbyjs.com/docs/how-to/querying-data/static-query/
Suggestion 2:
You might have a typo in the variable name "$limit" or you didn't provide the variable via context to this page query. Have a look
at the docs to learn how to add data to context:
https://www.gatsbyjs.com/docs/how-to/querying-data/page-query#how-to-add-query-variables-to-a-page-query
ERROR #85920 GRAPHQL
There was an error in your GraphQL query:
Variable "$skip" of required type "Int!" was not provided.
> 1 | query CategoryListQuery($ids: [String]!, $limit: Int!, $skip: Int!) {
| ^
2 | allMdx(
3 | filter: {id: {in: $ids}, frontmatter: {type: {in: "blog"}}}
4 | limit: $limit
5 | skip: $skip
6 | ) {
7 | edges {
8 | node {
9 | frontmatter {
10 | title
11 | date(formatString: "MMMM DD, YYYY")
File path: C:/gatsby/schtml/src/templates/categories.js
Url path: /blog/category/gatsby-develop
Plugin: none
Suggestion 1:
If you're not using a page query but a useStaticQuery / StaticQuery you see this error because they currently don't support
variables. To learn more about the limitations of useStaticQuery / StaticQuery, please visit these docs:
https://www.gatsbyjs.com/docs/how-to/querying-data/use-static-query/
https://www.gatsbyjs.com/docs/how-to/querying-data/static-query/
Suggestion 2:
You might have a typo in the variable name "$skip" or you didn't provide the variable via context to this page query. Have a look at the docs to learn how to add data to context:
https://www.gatsbyjs.com/docs/how-to/querying-data/page-query#how-to-add-query-variables-to-a-page-query
success Writing page-data.json files to public directory - 0.044s - 2/2 45.97/s
success run page queries - 0.422s - 3/3 7.12/s
success Writing page-data.json files to public directory - 0.059s - 3/3 50.83/s

UI5 OData Search Help not displaying values

this is the odata response i am getting:
my desired result would be a search help that displays the "TypRolle", "RolleNr" and "RolleOri" columns of the odata response. But right now i am stuck at trying to just display one of them, the "TypRolle" one.
this is what my search help looks like right now:
As you can see, no values are being displayed.
Here is my valueHelpRequest method:
var roletypeUrl = "my odata url";
var attModel = new sap.ui.model.json.JSONModel();
var link = this.getView().byId("roletype"); // roletype input field
var oDataModel = new sap.ui.model.odata.v2.ODataModel(roletypeUrl, {
json: true,
loadMetadataAsync: true
});
oDataModel.read("/ZshNsiAgr01xxlEinzelTypSet", {
success: function(oData, response) {
attModel.setData(oData);
var oValueHelpDialog = new sap.m.Popover({
title: "Rollentyp auswählen",
footer: [new sap.m.Button({
text: "Ok"
})]
});
oValueHelpDialog.openBy(link);
var oItemTemplate = new sap.m.StandardListItem({
title: "test",
description: "{TypRolle}"
});
var oHelpTable = new sap.m.Table({
width: "300pt",
columns: [
new sap.m.Column({
header: [
new sap.m.Label({
text: "Rollentyp"
})
]
})
],
items: {
path: "/results",
template: oItemTemplate
},
items: [
new sap.m.ColumnListItem({
cells: [
new sap.m.Text({
text: "{TypRolle}"
})
]
})
]
})
oHelpTable.setModel(attModel);
oValueHelpDialog.addContent(oHelpTable);
I am very thankful for any kind of suggestion and look forward to your answers :)
If you have to do it with JSON model ... here it is
]
})
]
})
oHelpTable.bindAggregation("items", "/results", new sap.m.ColumnListItem({
cells: [
new sap.m.Text({
text: "{TypRolle}"
})
]
}));
oHelpTable.setModel(attModel);
oValueHelpDialog.addContent(oHelpTable);
Or else you can directly bind to your default OData model as well and it can fetch the data automatically without you writing the read

Nunjucks nested object array field printing

My array data:
data = [
{ field: { name:"name1", title:"title1" } },
{ field: { name:"name2", title:"title2" } },
{ field: { name:"name3", title:"title3" } }
];
I want to write name fields like this:
Expected Output
name1.name2.name3
I need join these object's specified field values but I don't know how to get them like this.
What I tried and failed =>
data | selectattr("field") | selectattr("name") | join(".") }}
selectattr-filter only filter data elements. Therefore, when you apply selectattr('name'), nunjucks try to filter data by elements having name-field (none of them) and returns the empty result.
The simplest way to achive name1.name2.name3 is to use a custom filter
const nunjucks = require('nunjucks');
const env = nunjucks.configure();
env.addFilter('map', function (arr, prop, ...etc) {
const f = typeof prop == 'function' ?
prop : typeof env.filters[prop] == 'function' ?
env.filters[prop] : (e) => e[prop];
return arr instanceof Array && arr.map(e => f(e, ...etc)) || arr;
});
const data = [
{field: {name: "name1", title: "title1"}},
{field: {name: "name2", title: "title2"}},
{field: {name: "name3", title: "title3"}}
];
const html = env.renderString(`{{ data | map('field') | map('name') | join('.') }}`, {data});
console.log(html);

Bind children of children in SAPUI5 table

I have this model from my oData service:
{
"entity": [
{
"valueA": "ABC",
"valueB": "DEF",
"childL1": [
{
"valueC": "GHI",
"valueD": "JKL"
},
{
"valueC": "MNO",
"valueD": "PQR"
}
]
},
{
"valueA": "ABC",
"valueB": "DEF",
"childL1": [
{
"valueC": "GHI",
"valueD": "JKL"
},
{
"valueC": "MNO",
"valueD": "PQR"
}
]
}
]
}
Notice that for each 'child' in my 'entity' set, there's also an array of possible 'childL1'.
I have table in UI5 and I bound the data in the controller by:
this.myTable.bindAggregation('items', {path: /entity, template: this.oTemplate});
This works but the table displays:
Child1
Child2
However, what I want to do is:
/0/ChildL1/0
/0/ChildL1/0
/1/ChildL1/0
/1/ChildL1/0
So, to do that, I can do:
this.myTable.bindAggregation('items', {path: /entity/childL1, template: this.oTemplate});
The result would be as expected. However, I need to also display valueA in my table. Since my binding is at child1, I won't be able to get /entity/n/valueA.
What possible solution can I do for this? Is there a way to backtrack provided that childL1 has a 'key'? Or can I get entity then display childL1 in the table?
Since you haven't mentioned table control like which control you are using - sap.ui.table.Table or sap.m.Table. In case of "sap.ui.table.Table" you can bind the individual column like below
oTable.addColumn(new sap.ui.table.Column({
label : new sap.m.Label({
text : " "
}),
template : new sap.m.Text({
maxLines : 1
}).bindProperty("text", "valueA"),
}));
oTable.addColumn(new sap.ui.table.Column({
label : new sap.m.Label({
text : " "
}),
template : new sap.m.Text({
maxLines : 1
}).bindProperty("text", "childL1/valueC"),
}));
Like this you can add any no. of columns with different path but their parent path should alsways be same and then at last you can bind your main path to table rows like this-
oTable.bindAggregation("rows", {
path : "/entity"
});
and in case of sap.m.Table you can do it like this-
new sap.m.Table({ items:{
path:"/entity",
template: new sap.m.ColumnListItem({
cells:[
new sap.m.Text({
text:"{valueA}"
}),
new sap.m.Text({
text: "{childL1/valueC}"
})
]
})
}
})

How to present sort order-by query in Falcor?

Suppose the model is structured as
{
events: [
{
date: '2016-06-01',
name: 'Children Day'
},
{
date: '2016-01-01',
name: 'New Year Day'
},
{
date: '2016-12-25',
name: 'Christmass'
}
]
}
There could be a lot of events in our storage. From client side, we want to issue a query to get 10 events order by date in ascending order.
How to present this kind of query in Falcor?
Heres what I would do: first, promote your events to entities, i.e. give them ids:
id | date | name
1 | 2016-06-01 | Children Day
2 | 2016-01-01 | New Year Day
3 | 2016-12-25 | Christmass
Then provide a route providing these events by id:
route: 'eventsById[{integers:ids}]["date","name"]'
which returns the original data. Now you can create a new route for orderings
route: 'orderedEvents['date','name']['asc','desc'][{ranges:range}]
which returns references into the eventsById route. This way your client could even request the same data sorted in different ways within the same request!
router.get(
"orderedEvents.date.asc[0..2]",
"orderedEvents.date.desc[0..2]");
which would return
{
'eventsById': {
1: {
'date':'2016-06-01',
'name':'Children Day' },
2: {
'date':'2016-01-01',
'name':'New Year Day' },
3: {
'date':'2016-12-25',
'name':'Christmass' } },
'orderedEvents': {
'date': {
'asc': [
{ '$type':'ref', 'value':['eventsById',2] },
{ '$type':'ref', 'value':['eventsById',1] },
{ '$type':'ref', 'value':['eventsById',3] } ],
'desc': [
{ '$type':'ref', 'value':['eventsById',3] },
{ '$type':'ref', 'value':['eventsById',1] },
{ '$type':'ref', 'value':['eventsById',2] } ] } }
}

Resources