I want to make Nested list of shopping categories using data provided by server which looks something like this:
{
"status":{"statusCode":15001},
"data":[
{"itemId":1, "name":"Men", "subCategories":[
{"itemId":2, "name":"Clothes", "subCategories":[
{"itemId":3, "name":"Formals", "leaf":true,"subCategories":[]},
{"itemId":4, "name":"Casual", "leaf":true,"subCategories":[]},
{"itemId":5, "name":"Sports", "leaf":true,"subCategories":[]}
]},
{"itemId":6, "name":"Accessories", "subCategories":[
{"itemId":7, "name":"Formals", "leaf":true,"subCategories":[]},
{"itemId":8, "name":"Casual", "leaf":true,"subCategories":[]},
{"itemId":9, "name":"Sports", "leaf":true,"subCategories":[]}
]}
]},
{"itemId":10, "name":"Women", "subCategories":[
{"itemId":11, "name":"Clothes", "subCategories":[
{"itemId":12, "name":"Formals", "leaf":true,"subCategories":[]},
{"itemId":13, "name":"Casual", "leaf":true,"subCategories":[]},
{"itemId":14, "name":"Ethnic", "leaf":true,"subCategories":[]}
]},
{"itemId":15, "name":"Shoes", "subCategories":[
{"itemId":16, "name":"Hells", "leaf":true,"subCategories":[]},
{"itemId":17, "name":"Wedges", "leaf":true,"subCategories":[]},
{"itemId":18, "name":"Sports", "leaf":true,"subCategories":[]}
]}
]}
]
}
Since tree structure is wrapped in data element and children are wrapped in subCategories tag which is different from data so I wanted to pre-process this data such that it can be used by Nested List directly by making response like this:
{
"categories":[
{"itemId":1, "name":"Men", "categories":[
{"itemId":2, "name":"Clothes", "categories":[
{"itemId":3, "name":"Formals", "leaf":true,"categories":[]},
{"itemId":4, "name":"Casual", "leaf":true,"categories":[]},
{"itemId":5, "name":"Sports", "leaf":true,"categories":[]}
]},
{"itemId":6, "name":"Accessories", "categories":[
{"itemId":7, "name":"Formals", "leaf":true,"categories":[]},
{"itemId":8, "name":"Casual", "leaf":true,"categories":[]},
{"itemId":9, "name":"Sports", "leaf":true,"categories":[]}
]}
]},
{"itemId":10, "name":"Women", "categories":[
{"itemId":11, "name":"Clothes", "categories":[
{"itemId":12, "name":"Formals", "leaf":true,"categories":[]},
{"itemId":13, "name":"Casual", "leaf":true,"categories":[]},
{"itemId":14, "name":"Ethnic", "leaf":true,"categories":[]}
]},
{"itemId":15, "name":"Shoes", "categories":[
{"itemId":16, "name":"Hells", "leaf":true,"categories":[]},
{"itemId":17, "name":"Wedges", "leaf":true,"categories":[]},
{"itemId":18, "name":"Sports", "leaf":true,"categories":[]}
]}
]}
]
}
For this I was overriding getResponseData of reader but this method never gets called and no records are loaded in store. What am I doing wrong?
Here is my store:
Ext.define('MyTabApp.store.CategoriesStore',{
extend:'Ext.data.TreeStore',
config:{
model : 'MyTabApp.model.Category',
autoLoad: false,
storeId : 'categoriesStore',
proxy: {
type: 'ajax',
url: Properties.CONFIG_SERVICE_BASE_URL+'topnav/getall',
reader: {
type: 'json',
getResponseData: function(response) {
console.log("in getResponseData"); // Never logged in console
var rText = response.responseText;
var processed = Helper.replaceAll("data","categories",rText);
processed = Helper.replaceAll("subCategories","categories",processed);
var respObj = Ext.JSON.decode(processed);
return respObj.categories;
}
}
},
listeners:{
load: function( me, records, successful, operation, eOpts ){
console.log("categories tree loaded");
console.log(records); // This prints blank array
}
}
}
});
and here is model:
Ext.define('MyTabApp.model.Category', {
extend : 'Ext.data.Model',
config : {
idProperty : 'itemId',
fields : [
{name : 'itemId',type : 'int'},
{name : 'name',type : 'string'}
]
}
});
This is the list:
Ext.define('MyTabApp.view.CategoriesList', {
extend: 'Ext.dataview.NestedList',
alias : 'widget.categorieslist',
config: {
height : '100%',
title : 'Categories',
displayField : 'name',
useTitleAsBackText : true,
style : 'background-color:#999 !important; font-size:75%',
styleHtmlContent : true,
listConfig: {
itemHeight: 47,
itemTpl : '<div class="nestedlist-item"><div style="position:absolute; left:10px; top:10px; color:#222; font-size:130%">{name}</div></div>',
height : "100%"
}
},
initialize : function() {
this.callParent();
var me = this;
var catStore = Ext.create('MyTabApp.store.CategoriesStore');
catStore.load();
me.setStore(catStore);
}
});
What is best practice to process & format received data if it is not in the format we want and we don't have control over services?
I dont think you can attach getResponseData to reader's configuration. In past I have used the following approach.
Create an override of Ext.data.reader.Json like below. But the downside is that this will be called for all your proxies using json reader. and then add YourApp.override.data.reader.Json in the requires section of your application.
Ext.define('YourApp.override.data.reader.Json', {
override: 'Ext.data.reader.Json',
'getResponseData': function(response) {
// your implementation here
}
Alternatively you can make a Ext.Ajax.request and on success handler parse the data the way you want and then set the data in your store. You can also write this code in the "refresh" event or "beforeload" event of the store and return false to cancel the action
Related
The API endpoint I'm working with is returning data that has multiple nested relationships inside it, and I am using normalizeResponse() within DS.JSONAPISerializer to massage it into something that is fully JSON-API compliant.
The ember inspector shows that all data gets placed within its respective container correctly. The link between the top-level model and its hasMany child does work, but the link between the nested models does not work. I verify this by navigating within the inspector to the nested model's child model, clicking on it, and observing that its 'content' property is null.
First, take a look at how my models are set up:
// models/search.js
// i am able to browse from the search model to children with success
export default DS.Model.extend({
articles: DS.hasMany('article'),
});
// models/article.js
// i CANNOT browse from an article down to its digest in ember inspector
export default DS.Model.extend({
search: DS.belongsTo('search'),
type: DS.attr(),
created: DS.attr(),
updated: DS.attr(),
digest: DS.belongsTo('digest'),
});
// models/digest.js
export default DS.Model.extend({
title: DS.attr(),
desc: DS.attr(),
date: DS.attr(),
article: DS.belongsTo('article'),
});
Now, here's my modified JSON after my functions inside normalizeResponse complete. AFTER returning this data from normalizeResponse, the "digest" object under the parent "relationships" object disappears. Is there something wrong with my JSON? I've tried so many permutations of this with no success, and I am pretty sure this matches the JSON-API spec for Compound Documents.
{"data":{
"type":"searches",
"id":"17482738723",
"attributes":{
},
"relationships":{
"articles":{
"data":[
{
"type":"articles",
"id":"19988"
},
{
"type":"articles",
"id":"19989"
},
]
},
"digest":{
"data":[
{
"type":"digest",
"id":"19988_digest"
},
{
"type":"digest",
"id":"19989_digest"
},
]
}
}
},
"included":[
{
"id":"19988",
"type":"articles",
"attributes":{
"type": "internal",
"created":"2016-09-27T00:13:11.000Z",
"updated":"2016-09-27T00:13:11.000Z",
}
},
{
"id":"19988_digest",
"type":"digest",
"attributes":{
"title":null,
"desc":"four five six",
}
},
{
"id":"19989",
"type":"articles",
"attributes":{
"type": "internal",
"created":"2016-09-27T00:13:11.000Z",
"updated":"2016-09-27T00:13:11.000Z",
}
},
{
"id":"19989_digest",
"type":"digest",
"attributes":{
"title":"one two three",
"desc":null,
}
},
]
}
Your response indicates the following relationship model:
// models/search.js
export default DS.Model.extend({
articles: DS.hasMany('article'),
dignists: DS.hasMany('digest'),
});
// models/article.js
export default DS.Model.extend({
search: DS.belongsTo('search'),
});
// models/digest.js
export default DS.Model.extend({
search: DS.belongsTo('search'),
});
So you have to fix your response:
remove the digest relationship on the search
add a digest relationship to every article
So you will end with something like this:
{
"data":{
"type":"searches",
"id":"17482738723",
"attributes":{
},
"relationships":{
"articles":{
"data":[
{
"type":"articles",
"id":"19988"
},
{
"type":"articles",
"id":"19989"
},
]
}
}
},
"included":[
{
"id":"19988",
"type":"articles",
"attributes":{
"type": "internal",
"created":"2016-09-27T00:13:11.000Z",
"updated":"2016-09-27T00:13:11.000Z",
},
"relationships":{
"digest": {
"data": {
"type":"digest",
"id":"19988_digest"
}
}
}
},
{
"id":"19988_digest",
"type":"digest",
"attributes":{
"title":null,
"desc":"four five six",
},
"relationships":{
"digest":{
"data": {
"type":"digest",
"id":"19989_digest"
}
}
}
},
{
"id":"19989",
"type":"articles",
"attributes":{
"type": "internal",
"created":"2016-09-27T00:13:11.000Z",
"updated":"2016-09-27T00:13:11.000Z",
}
},
{
"id":"19989_digest",
"type":"digest",
"attributes":{
"title":"one two three",
"desc":null,
}
},
]
}
Know that you can also do it the other way around and specify the article on the digest. ember-data will automatically keep everything in sync. I personally prefer to specify both sides of the relationship for clarity.
I'm trying to get the value from a collection which looks a little confusing for me.
Collection looks like this
{
"_id" : "bqaGRGotKzYiA6mZv",
"0" : {
"MessageId" : "8B25CEB0-9EC6-48F7-9826-0AC813E903F8",
"Requested" : ISODate("2015-10-08T16:47:40.173Z")
},
"1" : {
"MessageId" : "B02935F0-8492-4858-B8C1-0BCEA9BCC80B",
"Requested" : ISODate("2015-10-08T16:59:45.503Z")
},
"2" : {
"MessageId" : "F766B029-BB81-4E6C-90B5-71B51B8F77FA",
"Requested" : ISODate("2015-10-08T16:47:22.956Z")
},
"3" : {
"MessageId" : "D88D87EE-CD25-4624-8265-8D66D8D9163A",
"Requested" : ISODate("2015-10-08T17:24:20.906Z")
}
}
Now I want to display to client a table with "Requested" values in 1 column
is there any other way than pointing each number manually?
This is the helper
myHelper: function(){
return Tasks.find();
}
and the code below will print the "ISODate("2015-10-08T16:47:40.173Z")"
{{#each myHelper}}
{{[0].Requested}}
{{/each}}
But I want some automatic action because I don't know how many numbers will be in collection.
I may not tell what you expect or need to accomplish your goal, but the collection organization approach you have taken feels a bit odd to me. So I'll post my answer, too.
If you had a collection item like this
{
"_id": "bqaGRGotKzYiA6mZv",
"messages": [{
"MessageId": "8B25CEB0-9EC6-48F7-9826-0AC813E903F8",
"Requested": ISODate("2015-10-08T16:47:40.173Z")
}, {
"MessageId": "B02935F0-8492-4858-B8C1-0BCEA9BCC80B",
"Requested": ISODate("2015-10-08T16:59:45.503Z")
}, {
"MessageId": "F766B029-BB81-4E6C-90B5-71B51B8F77FA",
"Requested": ISODate("2015-10-08T16:47:22.956Z")
}, {
"MessageId": "D88D87EE-CD25-4624-8265-8D66D8D9163A",
"Requested": ISODate("2015-10-08T17:24:20.906Z")
}]
}
or, even if you need to store items' IDs explicitly, like this:
{
"_id": "bqaGRGotKzYiA6mZv",
"messages": [{
"id": 0,
"MessageId": "8B25CEB0-9EC6-48F7-9826-0AC813E903F8",
"Requested": ISODate("2015-10-08T16:47:40.173Z")
}, {
"id": 1,
"MessageId": "B02935F0-8492-4858-B8C1-0BCEA9BCC80B",
"Requested": ISODate("2015-10-08T16:59:45.503Z")
}, {
"id": 2,
"MessageId": "F766B029-BB81-4E6C-90B5-71B51B8F77FA",
"Requested": ISODate("2015-10-08T16:47:22.956Z")
}, {
"id": 3,
"MessageId": "D88D87EE-CD25-4624-8265-8D66D8D9163A",
"Requested": ISODate("2015-10-08T17:24:20.906Z")
}]
}
then you would be able to provide the data from helper to the template like this:
myHelper: function(_id) {
return Tasks.find({
_id: _id
});
}
and use it like this:
{{#each myHelper}}
{{#each message}}
{{MessageId}}
{{Requested}}
{{/each}}
{{/each}}
You could also add messages items to a collection item like this:
var theMessage = {
"MessageId": "whatever",
"Requested": "whenever"
}
Tasks.update({
_id: _id
}, {
$push: {
messages: theMessage
}
});
Create global helper that will convert each object from your collection to an array.
Template.registerHelper("convertToArray", function(){
var self = this; //this points to single object from collection.
var array = [];
var keys = _.without(Object.keys(this), "_id");
keys.forEach(function(key){
array.push(self[key]);
})
return array;
});
Optionally you can sort keys before looping through it.
Then use it like this:
{{#each myHelper}}
{{#each convertToArray}}
{{Requested}}
{{/each}}
{{/each}}
I have this simple XML View:
<core:View xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m"
controllerName="listicons.list" xmlns:html="http://www.w3.org/1999/xhtml">
<Page title="Title">
<content>
<List id="test-list"></List>
</content>
</Page>
</core:View>
In my controller, I call a method to build list items onInit. First of all set some data:
var data = {
"products": [
{
"prodName": "Apple",
"prodCountry": "Netherlands",
"price": "normal"
},
{
"prodName": "Orange",
"prodCountry": "Spain",
"price": "extra"
},
{
"prodName": "Strawberry",
"prodCountry": "Poland",
"price": "normal"
}
]
};
// create a Model with this data and attach it to the view
var model = new sap.ui.model.json.JSONModel();
model.setData(data);
this.getView().setModel(model);
var list = this.getView().byId("test-list");
Then I build the list and bind the items to it:
// bind the List items to the data collection
list.bindItems({
path : "/products",
sorter : new sap.ui.model.Sorter("prodName"),
//template : listTmpl
template : new sap.m.StandardListItem({
title: "{prodName}",
description: "{prodCountry}"
})
});
After I built the list and is alread rendered, I look after which items have an extra price and set an icon for them:
jQuery.each(list.getItems(), function(i, obj) {
if(obj.mProperties.price == "extra") {
obj.setIcon("sap-icon://flag");
}
});
So. Everything works fine. But I am not happy with my solution, because I'd rather like to manipulate the data BEFORE rendering the list. I tried to build a list template directly before binding the items to the list and then use this template like:
var listTmpl = jQuery.each(data.products, function(i, a) {
var lI = new sap.m.StandardListItem({
title: "{prodName}",
description: "{prodCountry}"
});
if(a.price == "extra") {
lI.setIcon("sap-icon://flag");
}
return lI;
});
But then my list is not shown and I got an error in the console, saying
Missing template or factory function for aggregation items of Element sap.m.List ...
Does anyone have an idea how to improve my sol.?
THX a lot..
IMHO, I think you can have the controller as clean as possible, and define most of the needed functionality (binding, template, sorter, and icon) in the XMLView:
<List id="test-list" items="{
path : '/products',
sorter : [{
path : 'prodName',
descending : true
}]
}">
<StandardListItem title="{prodName}"
description="{prodCountry}"
icon="{path:'price', formatter:'.getIconFlag'}" />
</List>
You then can rid of all the template binding and manipulation stuff you have in your controller, and you only need to specify the formatter function getIconFlag:
getIconFlag : function (sPrice) {
return sPrice === "extra" ? "sap-icon://flag" : null;
}
See the following working example:
sap.ui.controller("view1.initial", {
onInit : function(oEvent) {
var oModel = new sap.ui.model.json.JSONModel();
oModel.setData({
"products": [
{
"prodName": "Apple",
"prodCountry": "Netherlands",
"price": "normal"
},
{
"prodName": "Orange",
"prodCountry": "Spain",
"price": "extra"
},
{
"prodName": "Strawberry",
"prodCountry": "Poland",
"price": "normal"
}
]
});
this.getView().setModel(oModel);
},
getIconFlag : function (sPrice) {
return sPrice === "extra" ? "sap-icon://flag" : null;
}
});
sap.ui.xmlview("main", {
viewContent: jQuery("#view1").html()
})
.placeAt("uiArea");
<script id="sap-ui-bootstrap"
src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
data-sap-ui-theme="sap_bluecrystal"
data-sap-ui-xx-bindingSyntax="complex"
data-sap-ui-libs="sap.m"></script>
<div id="uiArea"></div>
<script id="view1" type="ui5/xmlview">
<mvc:View
controllerName="view1.initial"
xmlns="sap.m"
xmlns:core="sap.ui.core"
xmlns:mvc="sap.ui.core.mvc" >
<List id="test-list" items="{
path: '/products',
sorter: [{
path: 'prodName',
descending: true
}]
}">
<StandardListItem title="{prodName}" description="{prodCountry}" icon="{path:'price', formatter:'.getIconFlag'}" />
</List>
</mvc:View>
</script>
bind the value for price to a formatter-function. so you can create the icons dynamically from the value
list.bindItems({
path : "/products",
sorter : new sap.ui.model.Sorter("prodName"),
template : new sap.m.StandardListItem({
title: "{prodName}",
description: "{prodCountry}",
/* bind items to factory-function */
icon: {
path: "price",
formatter: function(price) {
if (price == "extra") {
return "sap-icon://flag";
}
}
}
})
});
ps: i did not test this, but it should work like this. if you receive errors just comment.
This json object is returned via ajax.
[{"fiscal_year":2.012,"groupName":"Group1","Quantity":31666,"Revenue":"18,700,482.83"},
{"fiscal_year":2.013,"groupName":"Group1","Quantity":9264,"Revenue":"8003393.585"},
{"fiscal_year":2.014,"groupName":"Group1","Quantity":2483,"Revenue":"3,413,629.16"},
{"fiscal_year":2.012,"groupName":"Group2","Quantity":0,"Revenue":"3,055,922.45"},
{"fiscal_year":2.013,"groupName":"Group2","Quantity":0.00E+00,"Revenue":"3,916,824.84"},
{"fiscal_year":2.014,"groupName":"Group2","Quantity":0,"Revenue":"3,444,182.80"}
]
I need to modify it and save it as another json object so that I can display the years in columns.
$.ajax({
url: 'static/doc/sample.json',
type: 'GET',
dataType: 'JSON',
success: function(data){
var obj = [];
var business_group_nm = '';
$.each(data,function(key,value){
if(business_group_nm!=value.groupName){
obj.push({
"groupName": value.groupName,
"year":[value.fiscal_year:{
"Quantity":value.Quantity,
"Revenue":value.Revenue
}];
});
}else{
obj["year"].push(value.fiscal_year:{
"Quantity":value.Quantity,
"Revenue":value.Revenue
}];
});
}
business_group_nm = value.business_group_nm;
});
Final json object I want it to be:
[
{
"business_group_nm": "Group1",
"year": [
{
"2012": {
"Quantity": "31666",
"Revenue": "18,700,482.83"
}
},
{
"2013": {
"Quantity": "31666",
"Revenue": "18,700,482.83"
}
}
]
},
{
"business_group_nm": "Group2",
"year": [
{
"2012": {
"Quantity": "31666",
"Revenue": "18,700,482.83"
}
},
{
"2013": {
"Quantity": "31666",
"Revenue": "18,700,482.83"
}
}
]
},
];
But I got an error 'Uncaught SyntaxError: Unexpected token :'
"year":[value.fiscal_year:{
"Quantity":value.Quantity,
"Revenue":value.Revenue
}];
Thanks
obj["year"].push(value.fiscal_year:{
"Quantity":value.Quantity,
"Revenue":value.Revenue
}];
});
That just makes no sense, you can not use that kind of syntax “outside” of an actual object notation. And using .push is also wrong here, that is a method of Array, not for objects.
Something along the following lines should work to add a new property to obj["year"], using the fiscal_year value as property name and assign it an object as value:
obj["year"][value.fiscal_year] = {
"Quantity":value.Quantity,
"Revenue":value.Revenue
};
Edit: In this place,
"year":[value.fiscal_year:{
"Quantity":value.Quantity,
"Revenue":value.Revenue
}];
you are using the square brackets, which mean array, not object – but an array does not have any such “associative” keys. If you want this to be an object as well, then use curly braces here as well.
I have successfully implemented code for a JSONP request, retrieving data for multiple countries and displaying them as lines in a chart.
However, I would need to get the title, units, copyright etc. from the JSON as well, to be able to display that elements on the graph too.
Now, I wonder how this could be done.
The JSON response could look like this:
[
[
"series",
[
{
"data": [
[
2007,
2239300
],
[
2008,
2237490
],
[
2009,
2167070
],
[
2010,
2204450
]
],
"name": "France"
},
{
"data": [
[
2007,
2324234
],
[
2008,
3456352
],
[
2009,
1241422
],
[
2010,
4543231
]
],
"name": "Germany"
}
]
],
[
"title",
{
"text": "Title here"
}
],
[
"yAxis",
{
"text": "The units here"
}
]
]
My client's code would need to be changed then. For the moment it looks like this:
$.getJSON(url, {selectedCountries: "France,Germany,Switzerland", type: "jsonp"})
.done(function(data)
{
options.series = data;
var chart = new Highcharts.Chart(options);
})
.fail(function(jqxhr, textStatus, error)
{
var err = textStatus + ", " + error;
console.log( "Request Failed: " + err );
})
And I guess it must turn into something like this:
options.series = data['series']['data'];
options.title = data['title'];
But that doesn't work. Could anyone give me a hint what I should do? Thanks a lot!
Ok, got it going finally. One has to pass the JSON as an object (and not an array, and neither as string (so, no quotes like ' or " around the object!). Works like a charm here on fiddle.
Here the code:
$(function () {
var options = {
chart: {
renderTo: 'container',
type: 'spline',
marginBottom: 50
},
series: [{}]
};
data = {
"title": {
"text": "Here goes the title"
},
"yAxis": {
"title": {
"text": "Here go the units"
}
},
"series": [{
"name": "France",
"data": [[2006,2189260],[2007,2239300],[2008,2237490],[2009,2167070],[2010,2204450]]
}]
};
options.series = data["series"];
options.title = data["title"];
options.yAxis = data["yAxis"];
var chart = new Highcharts.Chart(options);
});
Thanks a lot for Sebastian Bochan's great support!